Factory & Strategy Pattern
Introduction
Developers in .NET sometimes come from scripting language environments that are not strong on Object Oriented Methodologies. OO methodologies like refactoring and using design patterns can be intimidating and the value for the developer is hard to see. What developers new to the OO world, and even more seasoned developers need to understand is that good design is not beyond scope in any project. Good habits simply must be learned that will make the developer a better designer.
In that mind set, I am submitting the first of several "how to" real
world examples, used in my professional life, on how to use patterns in a simple,
easy to follow manner. Many of us have ended up either writing or working on
in-line, scripting based code. Taking some ideologies from refactoring methodology,
this is not necessarily a bad thing. Of course, when you are first writing an
algorithm or a series of logic statements, it seems easier to start with if....then....else
.
But as you continue to expand this code to more complex forms, this model can
quickly become unmanageable. This is where refactoring and design pattern methodology
can play an important and useful part in simplifying, enhancing and making code
more useable (and understandable) to those who practice good OO design.
Using the code
This first effort will detail the use of a factory and several strategy
patterns. I have a long series of if....then....else
statements,
that contain algorithms that can be broken into classes. These algorithms each
have to use data from outside the statements, and based on this data will execute
separate logic statements from similar data.
if(i == 1){ p = x + y; } else if(i == 2){ p = x * y; } else if(...... //more algorithmic code.....
The variable i
is the switch or deciding logic for which statement
gets executed. Let us assume that the if....then....else
block
is quite long and has complex processing inside each statement. Let us also
assume that i
is a constant or a value that is not changeable outside
of the parameters of the code.
The first logical step might be to extract the if....then....else
algorithms out into classes, keeping in mind the rules of encapsulation, and
making sure to pass into the class only the information which is absolutely
needed. That is to say if you are passing around a heavy data object, but only
use a small amount of data from the parameters from that object, it stands to
reason that the constructor of your new classes (or methods on that class containing
your logic) would not accept the whole object, but only those parameter variables
values that were absolutely necessary, keeping in mind that no state was needed
to be maintained on said object. This gives a more visible and obvious code
flow, and prevents problems with unexpected modifications of the object or code.
But for this exercise, we are assuming that we are only passing around simple primitives, and no stateful object is implied. (If we were using a stateful object another design pattern might be more applicable, we will cover this in a later article.)
First, since all our algorithm classes need a base structure to allow them to be called in a similar fashion, we need to create a parent or abstract class. This class allows us to create a very similar way to call different classes that contain different algorithm logic in a similar manner. We will use this functionality later, to allow these classes, which are the beginnings of strategy pattern classes, to be created in a factory class, that can generate the code as we need to use it. The methods on the strategy classes will pass back an integer, which is the result of the algorithmic computation.
public abstract class AbstractArithmiticStrategy { public abstract int DoArithmitic(int x, int y); }
Your strategy classes could pass back any data you wanted, access a database,
modify a passed object somehow, or perhaps create different objects based on
the algorithm. (for more information see the DoFactory site for PatternStrategy.)
Generally though, in the strictest sense of the strategy pattern, your strategy
should fulfill some logical work for whatever code is passed into it. Lets say
each class from the if....then....else
needs to perform simple
arithmetic on the integer returned in a different way. Notice that we have inherited
from AbstractArithmiticStrategy
. This provides us with a way to
call the same method when pulling from the factory (we will see the example
later).
NOTE: Notice that we are using an attribute on each class. We will see in a little while why we are doing this.
[AlgorithmAttribute(AlgorithmType.Addition)] public class AdditionArithmiticStrategy : AbstractArithmiticStrategy { public int DoArithmitic(int x, int y) { return x + y; } } [AlgorithmAttribute(AlgorithmType.Multiplication)] public class MultiplicationArithmiticStrategy : AbstractArithmiticStrategy { public int DoArithmitic(int x, int y) { return x * y; } } [AlgorithmAttribute(AlgorithmType.Subtraction)] public class SubtractionArithmiticStrategy : AbstractArithmiticStrategy { public int DoArithmitic(int x, int y) { return x - y; } } [AlgorithmAttribute(AlgorithmType.Division)] public class DivisionArithmiticStrategy : AbstractArithmiticStrategy { public int DoArithmitic(int x, int y) { return x / y; } }
Now we have broken our algorithm code out from the if....then....else
and are only passing in the needed data, satisfying the requirements of good
encapsulation. But we still need a way to decide how to render the right code
at the right time. Since we know the variable i
is always going
to be a value we expect, lets convert the known values of i
to
an Enum. This will provide us with a structured way to pass the variable value
to the factory, and allows the factory to decide on the value of the enum which
algorithm class to procure to the executing code. We can also use an attribute
class to help the factory decide which class type to render.
enum AlgorithmType { Addition = 1; Subtraction = 2; Multiplication = 3; Division = 4; } [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public class AlgorithmAttribute : Attribute { private AlgorithmType _algorithmType; public AlgorithmAttribute(AlgorithmType algorithmType) { this._algorithmType = algorithmType; } public AlgorithmType TypeOfAlgorithm { get{return _algorithmType;} } }
Here we have the factory code. As you can see we register our known algorithm
classes with the factory at compile time. This is done to procure the class
types, so we can use reflection typing to render our classes from the factory.
We pass in the Enum AlgorithmType
and check it against the attribute
on each class.
public class AlgorithmStrategyFactory { private static ArrayList _registeredImplementations; static AlgorithmStrategyFactory() { _registeredImplementations = new ArrayList(); RegisterClass(typeof(AdditionArithmiticStrategy)); RegisterClass(typeof(MultiplicationArithmiticStrategy)); RegisterClass(typeof(SubtractionArithmiticStrategy)); RegisterClass(typeof(DivisionArithmiticStrategy)); } public static void RegisterClass(Type requestStrategyImpl) { if (!requestStrategyImpl.IsSubclassOf(typeof(AbstractArithmiticStrategy))) throw new Exception("ArithmiticStrategy must inherit from class AbstractArithmiticStrategy"); _registeredImplementations.Add(requestStrategyImpl); } public static AbstractArithmiticStrategy Create(AlgorithmType algorithmType) { // loop thru all registered implementations foreach (Type impl in _registeredImplementations) { // get attributes for this type object[] attrlist = impl.GetCustomAttributes(true); // loop thru all attributes for this class foreach (object attr in attrlist) { if (attr is AlgorithmAttribute) { if (((AlgorithmAttribute) attr).TypeOfAlgorithm.Equals(algorithmType)) { return (AbstractArithmiticStrategy) System.Activator.CreateInstance(impl); } } } } throw new Exception("Could not find a AbstractArithmiticStrategy implementation for this AlgorithmType"); } }
Now lets look at the functional code and compare it with the code we started with.
int p = (AlgorithmStrategyFactory.Create(algorithmType)).DoArithmitic(x,y);
This code looks very different from what we started with. First it is one line.
Second if we wished we could move this code to another area in which we needed
the same processing easily, and expect the same functionality (based on the
values of x
and y
). Of course there was a lot of work
to get to this point, and when you are refactoring code, you have to evaluate
whether doing refactoring in some areas is worth the effort. Remember design
patterns are only templates or ways of designing code to make it more workable
and maintainable, but you as the designer are going to have to decide what pattern
if any works for you in different instances. Whenever you refactor, remember
you should start with the smallest changes first, and make small refactorings,
the sum of which lead to a better overall design of code.
Points of Interest
For more info on refactoring visit the Martin Fowler site at http://www.martinfowler.com/ . For more information on design patterns for c# visit www.dofactory.com.
If this article helps you develop better code or for any comments or suggestions, please e-mail me at chris.lasater@gmail.com, or post a comment here.
History
This is the first article submission in a series that I am writing to CodeProject on this subject and is the first revision. Look for other articles on design patterns coming soon.
Related Articles
Other articles in this series include: