Today we will discuss about one of the most useful design patterns.
The Factory Method
It's a creational design pattern meaning that it helps us create objects efficiently.
Efficient object creation is a regular problem, that's why Factory Method is one of the most widely used design patterns.
By the end of this article, you will:
- Understand the core concepts of Factory Method
- Recognize opportunities to use Factory Method
- Learn to create a suitable implementation of Factory Method to suit your application
Definition
“One advantage of static factory methods is that, unlike constructors, they have names.” ― Joshua Bloch, Effective Java
Factory Method is a creational design pattern that provides an interface to create objects to a parent-class but allows child classes to modify the type of object that will be created.
Problem
Let's imagine that you have a shipping company. When it first started you only supported trucks.
In terms of code, it's gonna look like this:
- Shipping class — manages the shipping mechanism.
- Truck class — class that represents a truck.
class Truck:
def __init__(self, truck_id):
self.truck_id = truck_id
pass
def ship(self):
# Logic for shipping
pass;
class Shipping:
def __init__(self, info):
self.truck = Truck(info["truck_id"])
self.status = "Packaged"
self.package = info["package"]
pass
def send(self):
self.status = "Pending"
self.truck.ship()
# Initializing a shipping mission
data = {
"truck_id": 1,
"package": "some_package"
}
mission = Shipping(info=data)
mission.send()
As you can see, the shipping
class directly create a truck
object, making them very coupled to one another. The shipping
class would not work without a truck object.
Now, imagine that your shipping company became popular and clients are wanting to ship their products overseas. So naturally, you introduce a new type of transportation, the plane.
But, how will we be able to add that to our code?
A naive solution will be to check if the shipping
class uses a truck
or a plane
.
class Shipping:
def __init__(self, info, type):
self.type = type
if self.type == "truck":
self.truck = Truck(info["truck_id"])
self.status = "Packaged"
self.package = info["package"]
else:
self.plane = Plane(info["plane_id"])
self.status = "Packaged"
self.package = info["package"]
def send(self):
self.status = "Pending"
if self.type == "truck":
self.truck.ship()
else:
self.plane.ship()
This can get messy very quickly:
- The more the types of transportation, the more conditional code we have to write.
- Due to more code, we have added complexity.
- This breaks the single responsibility principle, the
shipping
class handles shipping of many different types of transportation. - This breaks the open-closed principle because every time we have to add a new type of transportation we have to edit existing code.
- Changing in any of the code in the
truck
orplane
class will require changes to theshipping
class.
Our problem here is that we don't exactly know what types of objects we will have, sometimes it's a truck, plane, or even a new type of transportation.
This is what the factory method pattern fixes.
Solution
The factory method pattern suggests that it's better to instantiate objects outside the constructor to a separate method and let subclasses control which objects get created.
So what we can do to our shipping class is:
- Remove the object instantiation from the constructor
- Make the
Shipping
class an abstract class - Add an abstract method
setTransport
that will set the appropriate transport - Create two new classes
TruckShipping
andPlaneShipping
that inherit fromShipping
class. - Implement the
setTransport
function in the new classes.
import abc # Python Library that allows us to create abstract classes
class Shipping(metaclass=abc.ABCMeta):
def __init__(self, package):
self.status = "Packaged"
self.package = package
self.transport = None
@abc.abstractmethod
def setTransport(self, data):
pass
class TruckShipping(Shipping):
def setTransport(self, id):
self.transport = Truck(id=id)
class PlaneShipping(Shipping):
def setTransport(self, id):
self.transport = Plane(id=id)
But what about the send
method we had in the first version of the Shipping
class. The first version of the send
method directly accessed the truck
object and ran the method ship
inside it.
Factory method pattern encourages us to create an interface or abstract class to our object types, to make them consistent and interchangeable.
Let's do another refactor:
- Create an abstract class called
Transport
with an abstract methodship
. - Create two new classes that inherit from
Transport
calledTruck
andPlane
.
class Transport(metaclass=abc.ABCMeta):
def __init__(self, id):
self.id = id
@abc.abstractmethod
def ship(self):
pass
class Truck(Transport):
def ship(self):
# Shipping instructions for trucks
pass
class Plane(Transport):
def ship(self):
# Shipping instructions for planes
pass
Finally in our Shipping abstract class, we can create a method called send
that calls the ship
method of a transport.
def send(self):
self.transport.ship()
When to use the pattern?
Design patterns should always be used within reason, as software engineers we need to be able to detect these opportunities.
For factory method pattern there are a couple of reasons why you might want to use it:
- When you don't know the exact types of objects your class will be interacting with. In our example, we could've had one transport system, or a hundred. You just don't know.
- Use factory method pattern when you want your users to extend the functionality of your library or framework.
- The factory method can also be used to save memory usage. Instead of creating a new object every time, you can save it in some sort of caching system.
Pros & Cons
Pros
- Removes tight coupling between classes.
- Increases readability.
- Allows us to construct subclasses with different properties than the parent class without using the constructor.
- Follows the single responsibility principle, making the code easier to support.
- Follows the open-closed principle, if you want to add a new type of transport, you can simply create a new subclass, without modifying existing code.
- Easier to test.
Cons
- The code becomes more complicated because you introduced many new subclasses.
Conclusion
Factory method is one of the most widely used design pattern, and one that you just might use next time at work!
In this article, you learned:
- What the factory method is, and it's components.
- How to refactor code, into the factory method pattern.
- Situations where factory method would be useful.
- Pros and cons of factory method
Further Reading
If you want to learn more about the design patterns, I would recommend Diving into Design Patterns. It explains all 23 design patterns found in the GoF book, in a fun and engaging manner.
Another book that I recommend is Heads First Design Patterns: A Brain-Friendly Guide, which has fun and easy-to-read explanations.
Top comments (1)
I have already been using the factory method but it is with your article that I can you know about its nunaces. Also, just a suggestion for cleaner code, in your naive solution example, you may want to take package and status initialisation out of if-else block.