Imagine that you are building an adventure game that uses procedural generation for building the world, and towns are one of the things which can be procedurally generated. Naturally, you would want to add some life to the village, so you create different kinds of villagers, each with their own model, behaviors, and so on.
After some time passes, it might start to become cumbersome to add or update any new ideas you have for villager types, not to mention the ball of code that is building up. When you have many similar objects that are being created, using a factory can help manage complexity.
Assumptions
- Basic programming knowledge
- Knowledge of <a href="https://www.devmaking.com/learn/design-patterns/design-pattern-modeling/" target="_blank" style="color:inherit;">Modeling Notation</a> and <a href="https://www.devmaking.com/learn/design-patterns/solid-principles/" target="_blank" style="color:inherit;">SOLID principles</a>
What is the Factory Pattern?
The factory pattern, often used in reference to both a factory method and a simple factory, is a way of instantiating complex objects that would be cumbersome to instantiate on their own in client code, in addition to being able to hide the concrete class from the implementation. This makes for a flexible environment.
Knowing both types of factories is important, as they both have real world practical use cases, each with their own tradeoffs.
A prominent use case that the factory patterns look to remedy preventing chains of if-else
statements in the client code such as this:
Villager villager;
if(type == "blacksmith") {
villager = new Villager("blacksmith.png", "Blacksmith", "I am the blacksmith.");
} else if (type == "farmer") {
villager = new Villager("farmer.png", "Farmer", "I am the farmer.");
} else if
//...
The main issue found with this is that it is difficult to extend and can pile up, making extension difficult, and leading to a breach in the Open Close Principle if new additions are made. Additionally, headache isn't far behind when these statements are copied and pasted in different parts of the code.
Factory Method
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/FactoryPattern_01.png" alt="Factory pattern UML" style="max-width:95%;"> </div>
A factory method is just that; a method; A concrete factory, which we'll call a producer, will inherit the method from a factory abstraction. This setup let's us do some cool stuff! Let's say we have some villagers and we want them to have different types of inventories based on their villager type; using a factory method called makeVillager
, we can have many different kinds of concrete classes that inherit the factory method.
// Villager interface.
interface IVillager {
void giveItem(String itemName, int quantity);
/*...*/
}
// Two types of villagers.
class Farmer implements IVillager {/*..*/}
class Blacksmith implements IVillager {/*..*/}
// Definition of a villager inventory factory
abstract class VillagerInventoryProducer {
/* Factory method classes can have as many functions as needed,
however only ONE method is going to be inherited by sub-classes,
which will be the factory method! */
public void someCoolMethod() {
IVillager v = this.makeVillager();
/*...*/
}
// Our factory method.
protected abstract IVillager makeVillager();
}
// Concrete producer for a farmer extending the factory.
class FarmerInventoryFactory extends VillagerInventoryProducer {
// Implementation of the concrete factory method.
IVillager makeVillager() {
IVillager farmer = new Farmer();
farmer.giveItem("corn seeds", 20);
farmer.giveItem("gold", 25);
return farmer;
}
}
// Concrete producer for a blacksmith.
class BlacksmithInventoryFactory extends VillagerInventoryProducer {
// Implementation of the concrete method.
IVillager makeVillager() {
IVillager blacksmith = new Blacksmith();
blacksmith.giveItem("iron", 5);
blacksmith.giveITem("gold", 30);
return blacksmith;
}
}
This is useful for when you want to construct an object that belongs to a group of similar objects without needing to explicitly specify it's concrete implementation. As we can now see, instantiating our villagers comes easily using the factory methods:
public static void main(String[] args) {
// villager type as specified by user input.
String villagerType = args[0];
// Our villager to be instanced.
IVillager villager;
// The producer which has our factory method.
VillagerInventoryProducer producer;
// Control statements.
if(villagerType == "blacksmith") {
producer = new BlacksmithInventoryFactory();
} else if(villagerType == "farmer") {
producer = new FarmerInventoryFactory();
} else if
//...
// Now that a producer has been picked, we can use its factory method.
villager = producer.makeVillager();
}
With this, creating a new type of villager is as simple as creating a new class and implementing a makeVillager()
method!
However, we're still depending on using an if-else
chain in the client code! There is another kind of factory that, though not always considered to be a real design pattern, helps to abstract this, and it's called a simple factory.
Simple Factory
A simple factory helps hide away chains of if-else
statements into a single "container" class. The benefit to doing this is that you no longer have to re-write the statements throughout the code, meaning changes are easier to make and there is a smaller chance of errors occurring.
The gist of a simple factory is just creating an interface and a single method which will delegate the creation of objects based on the input:
interface VillagerFactory {
// This is often created using an enum instead of string!
IVillager createVillager(String villagerType);
}
// The implementation of the simple factory
SimpleVillagerFactory implements VillagerFactory {
// Implementing our simple factory:
IVillager createVillager(String villagerType) {
if(villagerType == "blacksmith") {
return new Blacksmith();
} else if(villagerType == "farmer") {
return new Farmer();
} else if
//...
}
}
// Bonus: If we felt like making our factory method example more powerful..
InventoryVillagerFactory implements VillagerFactory {
IVillager createVillager(String villagerType) {
// Producer type
VillagerInventoryProducer producer;
if(villagerType == "blacksmith") {
producer = new BlacksmithInventoryFactory();
} else if(villagerType == "farmer") {
producer = new FarmerInventoryFactory();
} else if
//...
// With the producer set, we can return a villager with an inventory set.
return producer.makeVillager();
}
}
By offloading the logic to a simple factory, we only have to pass the string along to the method and get our villager instance! This makes the client code look much nicer than it did before by making it much more readable:
public static void main(String[] args) {
// villager type as specified by user input.
String villagerType = args[0];
// Our villager to be instanced.
IVillager basicVillager;
VillagerFactory basicFactory = new SimpleVillagerFactory();
basicVillager = basicFactory.createVillager(villagerType);
// Also: our bonus inventory implementation using the factory method:
IVillager inventoryVillager;
VillagerFactory inventoryFactory = new InventoryVillagerFactory();
inventoryVillager = inventoryFactory.createVillager(villagerType);
// Done!
}
If you're familiar with the <a href="https://www.devmaking.com/learn/design-patterns/solid-principles/" target="_blank" style="color:inherit;">SOLID principles</a>, there may be some alarm bells ringing about the simple factory; adding a new if-statement to the simple factory container violates the Open Close Principle!
Violating the Open Close Principle in Practice
The truth about SOLID principles is that, while they are often good guidelines for maintaining quality code, they sometimes need to yield to a higher principle: KISS!
The Open-Closed principle (OCP) can help developers design code with the future in mind. However, when following OCP, code can quickly become over-engineered in an attempt to make sure that the design is "open for extension and closed for modification". Where OCP might say to use the factory method, "Keep it simple, stupid" (KISS) might lean towards a simple factory depending on the situation.
Recommended Resources
- <a target="_blank" href="https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=devmaking-20&linkId=bc32087110669f75d93b216df79816f0" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Head First Design Patterns: A Brain-Friendly Guide</a> : An excellent primer for learning design patterns in a pragmatic way.
- <a target="_blank" href="https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=devmaking-20&linkId=ae25d94c4ea49870eb3115e0e4d2de90" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Design Patterns: Elements of Reusable Object-Oriented Software</a> : The reference guide made famous by Gang of Four, still widely used today.
- <a href="https://www.draw.io" target="_blank" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Draw.io</a>: A free, open-source tool for designing diagrams with built-in support for UML diagrams to make your own!