2. Factory 'Method' Pattern & 'Abstract' Factory Pattern
What is a ‘Factory’?
A factory, simply put, can be thought of as a vending machine. A ‘beverage’ vending machine will dispense ‘beverages,’ and a ‘snack’ vending machine will dispense ‘snacks.’ Since both the vending machine (VendingMachine) and the product (Product) are defined as interfaces, if you implement Product as a Beverage, then the VendingMachine should be implemented as a BeverageVendingMachine.
- Interface: ‘Vending Machine’ and ‘Product’
VendingMachine machine;
Product product;
- Class: ‘Beverage Vending Machine’ and ‘Beverage’
VendingMachine machine = new BeverageVendingMachine();
Product product = machine.get();
A factory, as shown above, means implementing based on the situation.
What is a ‘Factory Method’?
A factory method, as the name suggests, is a method that acts as a factory, implementing based on the situation. It’s a function that returns a Product implementation depending on what kind of Product it is, corresponding to machine.get() in the example above.
Factory Method Pattern
The Factory Method Pattern involves defining a ‘factory method’ within an interface and then creating concrete ‘implemented factory methods’ that return situation-specific implementation objects for various situations. Let’s examine the benefits of using this pattern.
- To Avoid: Returning Products Based on State - Internalizing State
- State management becomes difficult to maintain.
- Duplication in state management increases.
- Although this example only uses the
getfunction,if-elsestatements would repeat as more functions are added.
- Although this example only uses the
class VendingMachine {
public Product get(String type) {
if (type == "beverage") {
return new Beverage();
} else if (type == "snack") {
return new Snack();
}
}
}
VendingMachine machine = new VendingMachine();
Product product = machine.get('beverage');
- To Prefer: Returning Products Based on State - Externalizing State (Dependency Inversion)
- Centralized state management (resolves maintainability and duplication issues).
interface VendingMachine {
public Product get();
}
class BeverageVendingMachine implements VendingMachine {
public Product get() {
return new Beverage();
}
}
class SnackVendingMachine implements VendingMachine {
public Product get() {
return new Snack();
}
}
VendingMachine machine = new BeverageVendingMachine();
Product product = machine.get();
Within the VendingMachine interface, you simply need to implement how the factory method will return a specific implementation according to the situation. An implementation class for a no-longer-used situation can simply be deleted, and if additional situation-specific handling is needed, you just create a new class.
Abstract Factory Pattern
Now, let’s assume the variety of beverages is steadily increasing. If we use the Factory Method Pattern, a new class would be created every time. Is this really optimal? No matter how many types of beverages emerge, the category ‘Beverage’ itself doesn’t change. Only the contents vary.
interface VendingMachine {
public Product get();
}
class FruitBeverageVendingMachine implements VendingMachine {
public Product get() {
return new FruitBeverage();
}
}
class SparklingBeverageVendingMachine implements VendingMachine {
public Product get() {
return new SparklingBeverage();
}
}
class TeaBeverageVendingMachine implements VendingMachine {
public Product get() {
return new TeaBeverage();
}
}
...
FruitBeverage, SparklingBeverage, and TeaBeverage are all included in the ‘Beverage’ category, and in reality, the logic within these three internal classes would have only minor differences; most would be the same. If we create a generic Beverage class, we would only need to change the ingredients inside it. This solves the duplication problem of proliferating ...Beverage classes as follows:
interface VendingMachine {
Maker maker;
public Product get() {
return new Beverage(maker);
}
}
class FruitBeverageVendingMachine implements VendingMachine {
Maker maker = new FruitBeverageMaker();
}
class SparklingBeverageVendingMachine implements VendingMachine {
Maker maker = new SparklingBeverageMaker();
}
class TeaBeverageVendingMachine implements VendingMachine {
Maker maker = new TeaBeverageMaker();
}
...
While the ‘Factory Method Pattern’ has a factory “method” that returns situation-specific implementation objects, the ‘Abstract Factory Pattern’ has a method that simply returns a category object, and situation-specific detailed implementation is delegated to an “abstract” factory interface.
- The ‘Factory Method Pattern’ returns implementation objects 1, 2, 3 depending on the situation.
- The ‘Abstract Factory Pattern’ returns a Category A object,
- And the situation-specific implementation is delegated to the ‘Abstract Factory Interface’.
- The method mechanically returns the object and doesn’t need to know what it returns.
- In essence, the role of situation-dependent creation/return from the Factory Method Pattern has been further inverted via Dependency Inversion.
- And the situation-specific implementation is delegated to the ‘Abstract Factory Interface’.
So, isn’t proliferating ...Beverage classes effectively the same as proliferating ...BeverageMaker classes? Yes, it’s effectively the same. But why is the responsibility for situation-specific implementation delegated to the abstract factory interface?
This is to allow
VendingMachineto have only the role of providing products, whileBeverageMakershares the responsibility for product production.