How to add new derived types to a factory pattern?
Given basetype A
And three derived types B : A C : A D : A
I have a project P. I want a List to represent occurrences of A in P.
There is a strategy for extracting Bs from P, which I want to be part of class B. There is a strategy for extracting Cs from P, which I want to be part of clas开发者_如何转开发s C. etc. I want them all to be listed in one big List.
I want to later be able to add a class E : A, touching as little as possible. It would be cool to have a virtual static Factory method on A accepting instance of P as a parameter, which would polymorphically run all static overloads in the derived classes where for example the overload in C would extract Cs from P and put them in List. Of course, no such thing as a virtual static method exists in C#.
I can't immediately see how to implement this so that it would be possible to add classes D:A and E:A without touching the base class or some continuously updated "God-method" factory method with a concrete dependency to every derived type.
It's kinda late here, so I may be missing something obvious. Your thoughts?
EDIT:
My specific case is that I have a process plant control system consisting of control modules. I want to be able to recognize certain higher level constructs such as control loop, feedback tuning etc. The logic for recognizing and managing these constructs is specific to the individual construct type in question. In short, I want to bundle the code for identifying and for handling constructs.
Let's consider an analogy. I have a text document. In it there are types of words. Word is the "base" type "A". The text document is the "P" project. I can implement the word types "noun" and "verb". The way in which a "noun" is identified within the text is specific to "noun" and the code should. The List becomes longer as more and more types are implemented and thusly identified within the text.
To me, it makes sense to implement this within the noun class as:
static function IEnumerable<noun> IdentifyAll (P project)
And do a
CompleteWordList.AddRange(noun.IdentifyAll(p));
During initialization, but this creates a dependency to the specific "noun" type from the central initialization/factory method. It would then be difficult to add more word classes without touching it. As I'm writing this, I feel myself sort of leaning towards MEF.
This is a simple example. Maybe "phrase" or something would be a more appropriate analogy for the base type, but it'll have to do for now.
Even though these projects are networks of connected control nodes with inspectable properties (and not a text document as such), it very much feels like a parser. It would be good if there is some general solution that eludes me.
Okay, I think I get it now. If I understand, you want to take a an object of a type derived from A
and determine its actual type (say B
), but without the factory knowing how to classify it as B
. Then you should be able to introduce any number of subtypes of A
in future without having to modify the factory for each new type.
Someone has know how to get from A
to B
. If it isn't the factory, then it must be B
itself. I see you've gone down this path and thought "I need a virtual factory method so that each subtype of A can provide the specification to identify itself."
As you said, virtual static methods don't exist so you can't do it how you were thinking. But why not use an instance method?
As long as the project contains a list of all known subtypes, it can simply iterate through these types, creating instances of them and asking them "is this object one of you?". And if it says "yes", you can then ask it "give me an instance of you equivalent to this base type instance". (That sounds inefficient; in practice of course you could just keep one "factory instance" of each type alive so you don't have to keep recreating them.)
A contrived example using something like your word analogy:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
interface IWord
{
bool IsTypeOf(IWord word);
string Text { get; set; }
IWord MakeFrom(IWord word);
}
// the base type
class UntypedWord : IWord
{
public virtual string Text { get; set; }
public virtual bool IsTypeOf(IWord word)
{
throw new NotImplementedException();
}
public virtual IWord MakeFrom(IWord word)
{
throw new NotImplementedException();
}
}
// one specific subtype
class ColorWord : UntypedWord
{
public Color Color { get; private set; }
public override bool IsTypeOf(IWord word)
{
return word.Text == "red" || word.Text == "green" || word.Text == "blue";
}
public override IWord MakeFrom(IWord word)
{
var newMe = new ColorWord();
newMe.Text = word.Text;
if (word.Text == "red") newMe.Color = Color.Red;
else if (word.Text == "blue") newMe.Color = Color.Blue;
else if (word.Text == "yellow") newMe.Color = Color.Yellow;
return newMe;
}
}
// another specific type
class NumberWord : IWord // note: not an UntypedWord (see comments below)
{
public int Number { get; set; }
public string Text { get; set; }
public bool IsTypeOf(IWord word)
{
return word.Text == "one" || word.Text == "two" || word.Text == "three";
}
public IWord MakeFrom(IWord word)
{
var newMe = new NumberWord();
newMe.Text = word.Text;
if (word.Text == "one") newMe.Number = 1;
else if (word.Text == "two") newMe.Number = 2;
else if (word.Text == "three") newMe.Number = 3;
return newMe;
}
}
class WordList
{
Collection<Type> WordTypes = new Collection<Type>();
Collection<IWord> UntypedWords = new Collection<IWord>();
Dictionary<Type, Collection<IWord>> StronglyTypedWords = new Dictionary<Type, Collection<IWord>>();
public void AddWordType<T>() where T : IWord
{
WordTypes.Add(typeof(T));
if (!StronglyTypedWords.ContainsKey(typeof(T)))
StronglyTypedWords[typeof(T)] = new Collection<IWord>();
}
public void Add(IWord word)
{
bool foundType = false;
foreach (Type type in WordTypes)
{
// in practice you'd cache these factories for efficiency
IWord instance = Activator.CreateInstance(type) as IWord;
if (instance.IsTypeOf(word))
{
if (!StronglyTypedWords.ContainsKey(type))
StronglyTypedWords[type] = new Collection<IWord>();
StronglyTypedWords[type].Add(instance.MakeFrom(word));
foundType = true;
}
}
if (!foundType)
UntypedWords.Add(word);
}
public int HowManyWordsOfType<T>()
{
if (StronglyTypedWords.ContainsKey(typeof(T)))
return StronglyTypedWords[typeof(T)].Count;
return 0;
}
}
class Program
{
static void Main(string[] args)
{
var sentence = new WordList();
sentence.AddWordType<ColorWord>();
sentence.AddWordType<NumberWord>();
sentence.Add(new UntypedWord { Text = "two" });
sentence.Add(new UntypedWord { Text = "green" });
sentence.Add(new UntypedWord { Text = "frogs" });
sentence.Add(new UntypedWord { Text = "and" });
sentence.Add(new UntypedWord { Text = "one" });
sentence.Add(new UntypedWord { Text = "red" });
sentence.Add(new UntypedWord { Text = "rose" });
Console.WriteLine("color words: " + sentence.HowManyWordsOfType<ColorWord>());
Console.WriteLine("number words: " + sentence.HowManyWordsOfType<NumberWord>());
}
}
Output:
color words: 2
number words: 2
Now when you want to add a new word type, the only code you have to add is:
sentence.AddWordType<NewWordType>();
You'll notice that ColorWord
is a subtype of UntypedWord
but NumberWord
isn't. Word types don't have to share a common base type as long as they implement IWord.
Obviously this is a ridiculous example but it shows how each subtype owns the logic to classify itself based on properties of a given object of the common supertype (or common interface), and it knows how to create an instance of itself based on the given object.
So now the WordList
(your P
) class never has to be updated to accommodate new word types. You just need to tell it about all the types at runtime.
And you could write your IdentifyAll(WordList sentence)
method, only it would be an instance method instead of static.
So the real question here is how to create an instance of a derived type without knowing about the existence of the derived type; but what's important about the question is what the logic would be that would induce the creation of the previously unknown derived type. That is, there must be some logic that defines a new state for which the previously unknown derived type is appropriate; however, you want for the Factory to be unaware of that logic beforehand.
Effectively, your factory becomes a correlation system, that takes in a correlation description between a condition to be met and the type to create; then all the factory does is read the configuration (or utilize its Injected configuration) and execute whatever logic is defined to determine which creation code to use. It's a Factory as a data-driven object. Spring does something very similar to this; configuration of Spring determines the interrelationships between objects and the types which are appropriate to inject, and Spring just does what it's told. This type of system is complex, but is VERY extensible.
精彩评论