I'm refactoring some code around a couple of ASP.NET Web Forms pages and decided to try out a variation of the Abstract Factory pattern of my own design. I need to create an implementer of an abstract class based on what query string value is available.
Here's (a simplified version of) what I have right now:
internal static class Factory
{
public static AbstractClass Create(NameValueCollection queryString)
{
if (queryString["class1"] != null)
{
return new Class1(queryString["class1"]);
}
if (queryString["class2"] != null)
{
return new Class2(queryString["class2"]);
}
if (queryString["class3"] != null)
{
return new Class3(queryString["class3"]);
}
return null;
}
}
I don't know how I feel about this implementation. It certainly is better than what was there before, but I have a strong sense this could be improved some how.
So now, in the relevant pages, I can simply do this:
AbstractClass item = Factory.Create(Request.QueryString);
item.DoStuff();
Does anyone have any suggestions? Or would this be considered good as-is?
3 Answers 3
Passing a query string, or any string really, to the factory is troubling.
Why I avoid strings:
- typos
- CasEinG
- Presumed encoding/structure can be problematic.
IMHO the ideal will be to pass a parameter to the factory that unambiguously tells what to construct. As is, the factory is making gross assumptions about the content & validity of the incoming query string. But there is no telling what free-form text is in it.
The client code should evaluate & validate the query string and then pass the appropriate thing to the factory. And IMHO do not pass a string. Use enums
. enums
are:
- type safe
- avoid typos
- self documenting - all the possible class-types are defined here.
- Query string validation - now that we have a definitive list of all possible class-types we can verify the query string contents against the
enum
- intelisense in visual studio
Design thoughts
- Below is not the Abstract Factory Pattern per se. We are not creating factories of factories. The point of a factory is to separate "complex construction (code)" from the using class. The more complex the constructions "the more factory we need".
- Simple? then a static (or not) method w/in the client class is ok.
- Longish & somewhat complex? a separate class.
- Use the Factory in many places? a separate class might be best.
- Many variations of each class-type? Perhaps a full-blown abstract factory design. etc., etc, and etc.
- IMHO, making the
Factory
class an instance, static, or singleton is your call.
The Dictionary Examples
- Other examples using a dictionary are interesting. I'd use an
enum
for the key, not strings. overall it feels like the reflection/abstraction aspects are wasted as it's all internal and you still have to "open the factory", i.e. modify it, to add/delete class-types. - The dictionary is necessary only because we pass the entire query string to the factory. Again, Tell the factory what to build don't make it try to guess (see above about "gross assumptions"). The calling client is the absolute best place with the proper context to decide what to build.
- Making the factory parse the query string - and therefore handle problems - is violating the single responsibility principle.
.
internal public class Factory{
public AbstractClass Create(abstracts makeThis) {
AbstractClass newClass;
switch(makeThis) {
case abstracts.class1:
newClass = BuildClass1();
break;
case abstracts.class2:
newClass = BuildClass2();
break;
// and so on.
default:
throw new NotImplementedException();
break;
}
return newClass;
}
// build methods here.
}
public enum abstracts {class1, class2, class3}
public class Client{
AbstractClass factoryBuiltThing;
Factory classFactory = new Factory();
// put some error handling in here
protected bool ParseQueryString(string queryString, out buildThis) {}
// put some error handing here too.
protected abstracts DeriveAbstractEnum(string theDesiredClass){};
public void BuildSomething(string queryString) {
string buildThis = string.Empty;
abstracts whatToBuild;
if (ParseQueryString (queryString, out buildThis)) {
whatToBuild = DeriveAbstractEnum(buildThis);
factoryBuiltThing = classFactory.Create(whatToBuild);
}else {
throw new NotImplementedException(
string.format("The query string is crap: {0}", queryString));
}
}
}
-
\$\begingroup\$ I really like your point about the way I have it violating the single responsibility principle, excellent. \$\endgroup\$Sven Grosen– Sven Grosen2014年03月24日 01:18:04 +00:00Commented Mar 24, 2014 at 1:18
I Think you should do it more generic:
public static class Factory
{
static Dictionary<String, Func<String, Abstract>> classes;
public static Abstract GetInstance(String s){
if(!classes.ContainsKey(s)) throw new Exception();
Func<String, Abstract> create=classes[s];
return create(s);
}
static Factory ()
{
classes=new Dictionary<String, Func<String, Abstract>>(){
{"class1", (x)=>new Class1(x)},
{"class2", (x)=> new Class2(x)}
};
}
}
This code is way more readable.
You have the static constructor block, where you register the factory methods according to the keys. And the GetInstance
-Method shrinks to 3 lines, of which one is a purely guarding clause.
Edit: modified after Mat's Mug's hint.
P.S: Oh, I forgot to mention, that you really never should return null
.
Better a) throw an Exception or b) use a NullObject
.
-
1\$\begingroup\$ +1 but I'd probably use a Collection Initializer to initialize
classes
in the static constructor (in the end it's identical). \$\endgroup\$Mathieu Guindon– Mathieu Guindon2014年03月22日 02:51:19 +00:00Commented Mar 22, 2014 at 2:51 -
\$\begingroup\$ In your solution, where would the logic be to parse the query string value collection to figure out which class to initialize? \$\endgroup\$Sven Grosen– Sven Grosen2014年03月22日 16:55:43 +00:00Commented Mar 22, 2014 at 16:55
-
\$\begingroup\$ Nowhere. That's not business of the factory. That should be done before. The factory delivers upon order - so to say. To make the order is not its duty ;) \$\endgroup\$Thomas Junk– Thomas Junk2014年03月23日 10:16:05 +00:00Commented Mar 23, 2014 at 10:16
I would implement it using reflection.
I'm using a Dictionary
where the class types (inherited from AbstractClass
) are stored, using the query string key as key. Also a List
where I store keys, to keep track of sort order (the first inserted is the first checked for existence)
In the static constructor, the key and the type (for each class inherited from AbstractClass
) are added to the mentioned data containers.
The Create
method loops through all (sorted) keys contained in the _keys
list. As soon as a corresponding key is found in the query string, the class type associated to that key is retrieved from the dictionary, and an instance is dynamically created, passing in the value associated to the key in the query string.
Although there is more code than your current implementation, I think it's more elegant and generic, and less error prone.
internal static class Factory
{
private static readonly Dictionary<string, Type> _types = new Dictionary<string, Type>();
private static readonly List<string> _keys = new List<string>();
static Factory () {
// Note: the order in which elements are inserted into the dictionary
// determines which class has higher priority (in case multiple classes
// are specified in the name value collection
Add("class1", typeof(Class1));
Add("class2", typeof(Class2));
Add("class3", typeof(Class3));
}
private static void Add(string key, Type classType) {
_types[key] = classType;
_keys.Add(key);
}
/// Use this if the key corresponds to the class name
private static void Add(Type classType) {
Add(classType.Name, classType);
}
public static AbstractClass Create(NameValueCollection queryString) {
AbstractClass instance = null;
for (int count = 0; count < _keys.Count; ++count) {
var key = _keys[count];
var queryStringValue = queryString[key];
if (queryStringValue != null) {
var classType = _types[key];
var constructorParam = queryString[key];
var parameters = new string[] {constructorParam};
instance = (AbstractClass) Activator.CreateInstance(classType, parameters);
break;
}
}
return instance;
}
}
-
\$\begingroup\$ Your for loop has a problem:
++count
instead ofcount++
. I don't know how I feel about using reflection for this, but thank you for the answer; very interesting. \$\endgroup\$Sven Grosen– Sven Grosen2014年03月22日 16:58:28 +00:00Commented Mar 22, 2014 at 16:58 -
\$\begingroup\$
++count
andcount++
end up to the same result when used as standalone statements. I am used to the former because old unoptimized compilers translatedcount++
as:create a copy of count, increment it, then assign to count
, whereas ++count was translated as an atomicinc
assembler instruction. \$\endgroup\$Antonio– Antonio2014年03月23日 09:06:20 +00:00Commented Mar 23, 2014 at 9:06 -
\$\begingroup\$ wow, you are right; i did not know that. \$\endgroup\$Sven Grosen– Sven Grosen2014年03月24日 01:09:52 +00:00Commented Mar 24, 2014 at 1:09
?class1=someVal&class2=someOtherVal
? That's something to consider, since querystrings are client-facing and can be modified by them. \$\endgroup\$Factory
class? Why not make a static method on your Abstract class calledCreate
? There are examples of this in the .NET Framework. \$\endgroup\$switch
statement might be more efficient (cite) \$\endgroup\$