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:
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
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.
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
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
Here we are overriding the virtual
MakeADinosaur method, to return
Stegosaurus, which also implements the interface
(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:
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?
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:
Its constructor takes in an
IDinosaurFactory. This factory is then used to obtain an implementation of
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:
So, when we run the following code:
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
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.