What is the Factory Design Pattern?
The Factory Design Pattern is a creational pattern, used to instantiate objects.
It creates a single place to instantiate objects – eliminating duplicated code and the risk that objects get instantiated differently in different parts of the program.
Shared code for these examples
Both the code with the design pattern, and the code without the design pattern, will use this Monster class.
This class only has four properties, but a real Monster class in a game would probably have 15-20 properties: hit points, armor, weapons, spells, loot, experience points for defeating, etc.
namespace DesignPatternsCSharpNet6.Factory.Shared; public class Monster { public string Name { get; } public int AirSpeedMultiplier { get; } public int LandSpeedMultiplier { get; } public int WaterSpeedMultiplier { get; } public Monster(string name, int airSpeedMultiplier, int landSpeedMultiplier, int waterSpeedMultiplier) { Name = name; AirSpeedMultiplier = airSpeedMultiplier; LandSpeedMultiplier = landSpeedMultiplier; WaterSpeedMultiplier = waterSpeedMultiplier; } }
Source code without the Factory Design Pattern
Without using a factory, every place in our program where we need to instantiate an object would need to know all the parameters needed for the constructor. For a small class, that may be simple. But, for larger classes, that may involve many parameters – some of which may need their own setup.
In this example, let’s say the Monster class has a Loot property, holding a list of items the player would get from the monster after defeating it. In every place where we instantiate a Monster object, we’d need to call the code that gets the items to add to the Loot property, for that type of Monster.
With a lot of parameters or setup code, it’s common to make a mistake if you have to repeat the change in 5, 10, 20 places. It’s also common to accidentally forget to make the change in one place. This leads to strange bugs that only happen a small percent of the time – the time when the code runs through the instantiation with a problem.
This is the reason behind the DRY Principle – Don’t Repeat Yourself.
using DesignPatternsCSharpNet6.Factory.Shared; namespace DesignPatternsCSharpNet6.Factory.NonPattern; public class Tester { public void Test() { Monster cloudDragon = new Monster("Cloud Dragon", 2, 1, 1); Monster forestDragon = new Monster("Forest Dragon", 1, 2, 1); Monster seaDragon = new Monster("Sea Dragon", 1, 1, 2); } }
Source code with the Factory Design Pattern
To prevent the problems with repeated code, when instantiating objects, it’s common to use the Factory Design Pattern.
The basic concept is to have a single location (function) to instantiate objects.
In this example, I use a static class with a static function that will instantiate all Monster objects through the GetMonster function – which accepts a MonsterType value, to determine which type of Monster object to instantiate.
You could also have a separate function to instantiate each different type of Monster. For example, this class could have GetCloudDragon, GetForestDragon, and GetSeaDragon functions. A factory does not need to have only one function. It just needs to be written so other classes don’t ever call a “new” to instantiate an object. The other classes all call the Factory to do the object instantiation.
By having all instantiation of each class happen in a single location, you eliminate the risk of mistakes that often comes with repeated code.
using DesignPatternsCSharpNet6.Factory.Shared; namespace DesignPatternsCSharpNet6.Factory.Pattern; public static class MonsterFactory { public enum MonsterType { CloudDragon, ForestDragon, SeaDragon } public static Monster GetMonster(MonsterType monsterType) { switch (monsterType) { case MonsterType.CloudDragon: return new Monster("Cloud Dragon", 2, 1, 1); case MonsterType.ForestDragon: return new Monster("Forest Dragon", 1, 2, 1); case MonsterType.SeaDragon: return new Monster("Sea Dragon", 1, 1, 2); default: throw new ArgumentOutOfRangeException(nameof(monsterType)); } } }
In this Test class, the instantiation for the variables is handled by calling the factory method: MonsterFactory.GetMonster.
This way, if we ever need to change how we instantiate a Monster, we only need to change the code in one place – not after searching throughout our program, and (hopefully) not missing any place or making any typos.
using DesignPatternsCSharpNet6.Factory.Shared; namespace DesignPatternsCSharpNet6.Factory.Pattern; public class Tester { public void Test() { Monster cloudDragon = MonsterFactory.GetMonster(MonsterFactory.MonsterType.CloudDragon); Monster forestDragon = MonsterFactory.GetMonster(MonsterFactory.MonsterType.ForestDragon); Monster seaDragon = MonsterFactory.GetMonster(MonsterFactory.MonsterType.SeaDragon); } }
Summary
The Factory Design Pattern is a commonly used design pattern.
Something to remember with Factory pattern is that the return type does not need to be a single concrete datatype. You can have the Factory method return an interface, an abstract class, or a base class.
The source code is available at: https://github.com/CodingWithScott/DesignPatternsCSharpNet6