What is Factory Method?
Factory method is a creational pattern that helps in abstracting the process of object creation. What this means is that the class that uses the object is not necessarily the class that instantiates the object. In fact, the class that uses the object has absolutely no idea about the exact type of the object created! It just has a reference to the interface.We will understand what this means using a car manufacturing algorithm.
Example of Factory Method:
Lets see how we would build the cars in a non Design Pattern world. The first tab shows two car classes, Mazda and Ferrari. The CarBuilder builds the two cars. It has separate methods to build Ferrari and Mazda. It also has separate methods to set Color to it.
public class Ferrari { private String name; private Color color; public Ferrari(String name) { this.name = name; } public String getName() { return name; } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } } public class Mazda { private String name; private Color color; public Mazda(String name) { this.name = name; } public String getName() { return name; } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } }
public class CarBuilder1 { Ferrari ferrari; Mazda mazda; public void buildFerrari(String name) { ferrari = new Ferrari(name); } public void buildMazda(String name) { mazda = new Mazda(name); } public void paintMazda(Color color) { mazda.setColor(color); } public void paintFerrari(Color color) { ferrari.setColor(color); } }
public class CarBuilderClient1 { public static void main(String[] args) { CarBuilder1 builder = new CarBuilder1(); builder.buildFerrari("Ferrari488"); builder.paintFerrari(Color.red); builder.buildMazda("mazdamx5"); builder.paintMazda(Color.green); } }
Lets list out the problems with these:
- The CarBuilder class cannot incorporate change easily. If you get a new car type, you need to change the class to add methods to create the new car type.
- The CarBuilder stores reference to the concrete car implementation. That’s not a good design practice.
- Lets say you have a new version of Mazda that extends the original vesion. Mazda2 extends Mazda. With this implementation, you would have to change the Builder class.
To solve these problems, we do the following
- We have the Cars extend a common interface. The Builder then stores the reference to the Car interface (and not to the concrete implementations). We can now add new cars and the builder does not have to change.
- We extend the CarBuilder (now abstract) and provide CarBuilders for both Mazda and Ferrari. The instantiation of the cars is done in the concrete implementations.
Class diagram for car builder using Factory Method
What have we changed?
Here are the classes
public interface Car { public String getName(); public void setColor(Color color); } public class Ferrari implements Car { private String name; private Color color; public Ferrari(String name) { this.name = name; } public String getName() { return name; } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } } public class Mazda implements Car { private String name; private Color color; public Mazda(String name) { this.name = name; } public String getName() { return name; } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } }
public abstract class CarBuilder { Car car; public abstract Car buildCar(); public void paintCar(Color color) { car.setColor(color); } } public class FerrariCarBuilder extends CarBuilder { @Override public Car buildCar() { car = new Ferrari("Ferrari"); return car; } } public class MazdaCarBuilder extends CarBuilder { @Override public Car buildCar() { car = new Mazda("Mazda"); return car; } }
public class CarBuilderClient { CarBuilder carBuilder; Car car; public static void main(String[] args) { CarBuilderClient client = new CarBuilderClient(); client.execute(); } public void execute() { setCarBuilder(new FerrariCarBuilder()); buildAndPaintCar(Color.BLACK); } private void buildAndPaintCar(Color color) { car = carBuilder.buildCar(); carBuilder.paintCar(color); } public void setCarBuilder(CarBuilder carBuilder) { this.carBuilder = carBuilder; } }
Another way to implement factory method is to use a parameterized constructor for the Car. In the example below we use a single CarBuilder, but the type of car to be instantiated depends upon a parameter passed in the factory method buildCar
public class CarBuilderParameterized { public Car buildCar(String type, String name) { if ("ferrari".equals(type)) { return new Ferrari(name); } else if ("mazda".equals(name)) { return new Mazda(name); } return null; } public void paintCar(Car car, Color color) { car.setColor(color); } }
public class CarBuilderClientParameterized { public static void main(String[] args) { CarBuilderClientParameterized parameterizedCarBuilder = new CarBuilderClientParameterized(); parameterizedCarBuilder.execute(); } private void execute() { CarBuilderParameterized carBuilderParameterized = new CarBuilderParameterized(); Car car1 = carBuilderParameterized.buildCar("ferrari", "ferrari458"); car1.setColor(Color.black); Car car2 = carBuilderParameterized.buildCar("mazda", "mazdaRX6"); car2.setColor(Color.red); } }
To sum up, use factory method to create new objects so that the process of object creation is encapsulated and separated from the class that uses that objects.