At this point, you've probably already encountered design patterns. For example, singleton.
Let's recall what patterns are, why they are needed, and what creational patterns are (singleton is an example). We'll also study a new pattern: the factory method.
In software development, a design pattern is a repeatable architectural construct that represents a solution to a design problem within some recurring context.
Typically, a pattern is not a final solution that can be directly converted to code. It is just a model solution to a problem that can be used in various situations.
Creational patterns are design patterns that deal with the process of creating objects. They make it possible to create a system that is independent of the method used to create, compose, and present objects.
A factory method is a creational design pattern that defines a common interface for creating objects in a parent class, giving its descendants the ability to create these objects. At creation time, descendants can determine which class to create.
What problem does the pattern solve?
Imagine that you decide to create a delivery program. Initially, you will hire couriers with cars and use a Car object to represent a delivery vehicle in the program. Couriers deliver packages from point A to point B and so on. Easy peasy.
The program is gaining popularity. Your business is growing and you want to expand into new markets. For example, you could start also delivering food and shipping freight. In this case, food can be delivered by couriers on foot, on scooters, and on bicycles, but trucks are needed for freight.
Now you need to keep track of several things (when, to whom, what and how much will be delivered), including how much each courier can carry. The new modes of transportation have different speeds and capacities. Then you notice that most entities in your program are strongly tied to the Car class. You realize that in order to make your program work with other delivery methods, you will have to rewrite the existing code base and do that again every time you add a new vehicle.
The result is horrendous code filled with conditional statements that perform different actions depending on the type of transportation.
The solution
The factory method pattern suggests creating objects by calling a special factory method rather than directly using the new operator. Subclasses of the class that has the factory method can modify the created objects of the specific vehicles. At first glance, this may seem pointless: we have simply moved the constructor call from one place in the program to another. But now you can override the factory method in a subclass to change the type of transportation being created.
Let's look at the class diagram for this approach:
For this system to work, all the returned objects must have a common interface. Subclasses will be able to produce objects of different classes that implement this interface.
For example, the Truck and Car classes implement the CourierTransport interface with a deliver method. Each of these classes implements the method in a different way: trucks deliver freight, while cars deliver food, packages, and so on. The factory method in the TruckCreator class returns a truck object, and the CarCreator class returns a car object.
For the client of the factory method, there is no difference between these objects, since it will treat them as some kind of abstract CourierTransport. The client will care deeply that the object has a method for delivering, but how exactly that method works is not important.
Implementation in Java:
public interface CourierTransport {
void deliver();
}
public class Car implements CourierTransport {
@Override
public void deliver() {
System.out.println("The package is being delivered by car");
}
}
public class Truck implements CourierTransport {
@Override
public void deliver() {
System.out.println("The freight is being delivered by truck");
}
}
public abstract class CourierTransportCreator {
public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
@Override
public CourierTransport createTransport() {
return new Car();
}
}
public class TruckCreator extends CourierTransportCreator {
@Override
public CourierTransport createTransport() {
return new Truck();
}
}
public class Delivery {
private String address;
private CourierTransport courierTransport;
public void Delivery() {
}
public Delivery(String address, CourierTransport courierTransport) {
this.address = address;
this.courierTransport = courierTransport;
}
public CourierTransport getCourierTransport() {
return courierTransport;
}
public void setCourierTransport(CourierTransport courierTransport) {
this.courierTransport = courierTransport;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
public static void main(String[] args) {
// Accept a new type of order from the database (pseudocode)
String type = database.getTypeOfDeliver();
Delivery delivery = new Delivery();
// Set the transport for delivery
delivery.setCourierTransport(getCourierTransportByType(type));
// Make the delivery
delivery.getCourierTransport().deliver();
}
public static CourierTransport getCourierTransportByType(String type) {
switch (type) {
case "CarDelivery":
return new CarCreator().createTransport();
case "TruckDelivery":
return new TruckCreator().createTransport();
default:
throw new RuntimeException();
}
}
If we want to create a new delivery object, then the program automatically creates an appropriate transport object.
When should we apply this pattern?
1. When you do not know in advance the types and dependencies of the objects that your code needs to work with.
The factory method separates the code for producing forms of transportation from the code that uses the transportation. As a result, the code for creating objects can be extended without touching the rest of the code.
For example, to add support for a new type of transportation, you need to create a new subclass and define a factory method in it that returns an instance of the new transport.
2. When you want to save system resources by reusing existing objects instead of creating new ones.
This problem usually occurs when working with resource-intensive objects, such as database connections, file systems, etc.
Think of the steps you need to take to reuse existing objects:
First, you need to create a shared repository to store all the objects you create.
When requesting a new object, you need to look in the repository and check whether it contains an available object.
Return the object to the client code.
But if there are no available objects, create a new one and add it to the repository.
All this code needs to be put somewhere that won't clutter up the client code. The most convenient place would be the constructor, since we only need all these checks when creating objects. Alas, a constructor always creates a new object — it cannot return an existing object.
That means that another method is needed that can return both existing and new objects. This will be the factory method.
3. When you want to allow users to extend parts of your framework or library.
Users can extend your framework classes through inheritance. But how do you make the framework create objects of these new classes rather than the standard ones?
The solution is to let users extend not only the components, but also the classes that create those components. And for this, the creating classes must have specific creation methods that can be defined.
Advantages
- Decouples a class from specific transportation classes.
- Keeps the code for creating forms of transportation in one place, making the code easier to maintain.
- Simplifies the addition of new modes of transportation to the program.
- Implements the open-closed principle.
Disadvantages
May lead to large parallel class hierarchies, since each product class must have its own creator subclass.
Let's summarize
You learned about the factory method pattern and saw one possible implementation. This pattern is often used in various libraries that provide objects for creating other objects.
Use the factory method pattern when you want to easily add new objects of subclasses of existing classes in order to interact with your main business logic and not bloat your code due to different contexts.
GO TO FULL VERSION