Design Patterns in C# - Factory Method and Abstract Factory
I've been focusing lately on code quality, refactoring and architecture. A large part of my reading (& Pluralsight course-ing) has been based around design patterns, and how the application of these patterns can help bring your code in line with the SOLID principles. This means that code which uses these patterns is often more extensible, robust and testable. So I thought I'd write a (heavily dinosaur based) blog series, running through the main design patterns used in C#, starting (where I think makes the most sense) with the creational design patterns!
Creational design patterns are those which abstract away the creation of new objects. I.e. they are an attempt to stamp out the use of the "new" keyword, which couples classes together, and violates the Open-Closed principle.
The two that I am going to focus on in this blog are the factory method and abstract factory design patterns.
So, if we start with this SingleDinosaurNoiseProducer
class which makes a dinosaur noise:
public class SingleDinosaurNoiseProducer : IDinosaurNoiseProducer
{
public void MakeADinosaurNoise()
{
IDinosaur dinosaur = new TRex();
dinosaur.MakeANoise();
}
}
We can see that here, an instance of a TRex
is just new-ed up in the body of the code, so this class will always make a TRex
noise.
public class TRex : IDinosaur
{
public void MakeANoise()
{
Console.WriteLine("RAWWR!");
}
}
In order for to have a choice over which dinosaur noise to make, we need to be able to add new dinosaurs, without changing the code which decides when to make the noise. To do this, we must abstract away the creation of the dinosaur.
Factory Method
The factory method is as it states, a method. It takes the creation of objects and moves it out of the main body of the code. This means that in derived classes, this method can be overridden and therefore the behaviour can be extended. Also, when used with an interface, it abstracts away the exact implementation of the object returned.
For example, using the class TRexNoiseProducer
:
public class TRexNoiseProducer : IDinosaurNoiseProducer
{
public void MakeADinosaurNoise()
{
IDinosaur dinosaur = MakeADinosaur();
dinosaur.MakeANoise();
}
public virtual IDinosaur MakeADinosaur()
{
return new TRex();
}
}
The class now has a factory method called MakeADinosaur
that returns an IDinosaur
. In this class we return a TRex
. So, if we call MakeADinosaurNoise
on the TRexNoiseProducer
class, then "RAWWR!" will be written out to the console.
However, as the MakeADinosaur
method is virtual we can override this behaviour. If we have a class StegosaurusNoiseProducer
, which derives from TRexNoiseProducer
:
public class StegosaurusNoiseProducer : TRexNoiseProducer
{
public override IDinosaur MakeADinosaur()
{
return new Stegosaurus();
}
}
Here we are overriding the virtual MakeADinosaur
method, to return Stegosaurus
, which also implements the interface IDinosaur
:
public class Stegosaurus : IDinosaur
{
public void MakeANoise()
{
Console.WriteLine("Squeak?");
}
}
(I have to admit that it was around this point which I realised that I wasn't entirely sure what noise a stegosaurus did make, so this will have to do...) Therefore, when using the StegosaurusNoiseProducer
class, then this derived class' factory method will be called, and when you call MakeADinosaurNoise
, then "Squeak?" will be printed out to the command line instead.
Running the following:
public static void FactoryMethodExamples()
{
IDinosaurNoiseProducer fm1 = new TRexNoiseProducer();
fm1.MakeADinosaurNoise();
IDinosaurNoiseProducer fm2 = new StegosaurusNoiseProducer();
fm2.MakeADinosaurNoise();
}
Would first write out "RAWWR!" then "Squeak?" to the console.
All that would be needed to produce some different behaviour would be to add another noise producer class and override the MakeADinosaur
method, returning a different concrete implementation of IDinosaur
, with the correct behaviour.
However, here the calling code must still know which dinosaur it's going to return. We at some point have to decide which dinosaur we are going to produce, and then we are given a factory which produces that one dinosaur (In this example I've just instantiated the noise producers, but they could be passed in through dependency injection). What if we needed to be able to create a random dinosaur? What if we didn't want to know what dinosaur noise we were going to get at any different moment?
Abstract Factory
An abstract factory is similar to the factory method, but instead of a method it is an object in its own right.
Using an abstract factory, we can make a DinosaurNoiseProducer
class, which looks like this:
public class DinosaurNoiseProducer : IDinosaurNoiseProducer
{
private IDinosaurFactory dinosaurFactory;
public DinosaurNoiseProducer(IDinosaurFactory dinosaurFactory)
{
this.dinosaurFactory = dinosaurFactory;
}
public void MakeADinosaurNoise()
{
IDinosaur dinosaur = this.dinosaurFactory.CreateADinosaur();
if (dinosaur != null)
{
dinosaur.MakeANoise();
}
}
}
Its constructor takes in an IDinosaurFactory
. This factory is then used to obtain an implementation of IDinosaur
.
When you create the factory, you tell it which type of dinosaur it will create. This is then parsed as an enum, which looks like this:
public class DinosaurFactory : IDinosaurFactory
{
private readonly DinosaurType dinosaurType;
public DinosaurFactory(string dinosaurType)
{
Enum.TryParse(dinosaurType, true, out this.dinosaurType);
}
public IDinosaur CreateADinosaur()
{
switch (this.dinosaurType)
{
case DinosaurType.TRex:
return new TRex();
case DinosaurType.Stegosaurus:
return new Stegosaurus();
}
return null;
}
}
So, when we run the following code:
public static void AbstractFactoryExamples(string dinosaurType)
{
IDinosaurNoiseProducer af1 = new AbstractFactory.DinosaurNoiseProducer(new DinosaurFactory(dinosaurType));
af1.MakeADinosaurNoise();
}
Then if we pass in "trex" then we the producer will be returned a returned a TRex
, so when we call MakeADinosaurNoise
, "RAWWR!" will be printed out to the console. Similarly we could pass in "stegosaurus" and a stegosaurus noise will be produced.
All that we would need to do here in order to add new dinosaurs would be to add another value to the enum, corresponding to the new IDinosaur
with the desired behaviour, and add case to the factory. This can also be combined with reflection (through which you can discover the available classes) so that you don't need to add each case by hand. Here we can see that the dinosaur type could just be set in configuration which would allow you to decide on behaviour via configuration at run time. So here we have reproduced the previous behaviour, but reduced the need for a complex class hierarchy.
In order to create our random dinosaur factory we need a different sort of factory, a RandomDinosaurFactory
:
public class RandomDinosaurFactory : IDinosaurFactory
{
private Random random;
RandomDinosaurFactory(Random random)
{
this.random = random;
}
public IDinosaur CreateADinosaur()
{
int randomNumber = random.Next(0, 10);
if (randomNumber < 5)
{
return new TRex();
}
else
{
return new Stegosaurus();
}
}
}
Which would produce a random dinosaur each time you called it.
The different concrete implementations of IDinosaurFactory
can be passed in via dependency injection, which makes this solution extremely flexible and customizable. Here we have altered the decision around which dinosaur noise will be produced, without having to change either the code producing the dinosaur noise, or the code which chooses when the noise will be made.
If you want to see the overall code for these examples, here's the GitHub repo! That's all for now, watch out for my next blog on design patterns in C#!