Creational Design Patterns
In the last chapter, we used the Factory Method to create a single Transport object. But what if you need to create a whole set of related objects that are designed to work together?
Consider a cross-platform application that needs to support different UI themes, like Windows and macOS. A consistent theme requires that all UI elements match. You need a WindowsButton, a WindowsCheckbox, and a WindowsTextField for the Windows theme. For macOS, you need a MacButton, a MacCheckbox, and a MacTextField.
How can your application create a full UI without scattering if (os == 'windows') logic everywhere? More importantly, how do you guarantee you don't accidentally create a MacCheckbox in a Windows-themed window? The client code needs a way to say, "Give me the complete set of UI elements for the current theme," and be sure they are all from the same family.
The Abstract Factory is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.
The key word here is families. While the Factory Method deals with creating one type of product, the Abstract Factory deals with creating a suite of different products that are designed to be used together. You can think of it as a "factory for factories," where the Abstract Factory defines the contract for creating a whole set of related items.
This pattern has more moving parts than the Factory Method:
IButton, ICheckbox).WindowsButton, MacButton, WindowsCheckbox, MacCheckbox).IGuiFactory with createButton() and createCheckbox() methods).AbstractFactory interface. It knows how to create all the products for one specific, consistent theme (e.g., WindowsFactory creates WindowsButton and WindowsCheckbox).Let's implement our cross-platform UI factory.
Step 1: Define the Abstract Products
public interface IButton { void paint(); }
public interface ICheckbox { void paint(); }
Step 2: Create the Concrete Product Families
// Windows family
public class WindowsButton implements IButton { /* ... */ }
public class WindowsCheckbox implements ICheckbox { /* ... */ }
// macOS family
public class MacButton implements IButton { /* ... */ }
public class MacCheckbox implements ICheckbox { /* ... */ }
Step 3: Define the Abstract Factory Interface
This is the contract for any factory that can create a full set of UI elements.
public interface IGuiFactory {
IButton createButton();
ICheckbox createCheckbox();
}
Step 4: Create the Concrete Factories
Each factory knows how to create a matched set of products.
public class WindowsFactory implements IGuiFactory {
@Override public IButton createButton() { return new WindowsButton(); }
@Override public ICheckbox createCheckbox() { return new WindowsCheckbox(); }
}
public class MacFactory implements IGuiFactory {
@Override public IButton createButton() { return new MacButton(); }
@Override public ICheckbox createCheckbox() { return new MacCheckbox(); }
}
Step 5: The Client is Configured with a Factory
The client code is configured with a specific factory at runtime. It is completely decoupled from the concrete UI elements and only works with the abstract interfaces.
public class Application {
private final IButton button;
private final ICheckbox checkbox;
public Application(IGuiFactory factory) {
// The application receives a factory and uses it to create its UI elements.
// It doesn't know the concrete types of the elements it's creating.
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void paint() {
button.paint();
checkbox.paint();
}
}
public class ApplicationConfigurator {
public static void main(String[] args) {
// Configuration can come from a file, environment variable, etc.
String osName = System.getProperty("os.name").toLowerCase();
IGuiFactory factory;
if (osName.contains("win")) {
factory = new WindowsFactory();
} else if (osName.contains("mac")) {
factory = new MacFactory();
} else {
throw new UnsupportedOperationException("Your OS is not supported.");
}
Application app = new Application(factory);
app.paint();
}
}
Benefits:
LinuxFactory) without changing the client code. It also promotes DIP by making the client depend on the abstract factory interface, not concrete factories.Drawbacks:
LinuxFactory), it is very difficult to add a new product type (like a ITextField). Doing so would require changing the IGuiFactory interface, which would force you to modify all of your existing concrete factory classes. This is a critical point to raise in an interview.createButton(), createCheckbox()) are themselves Factory Methods.WindowsFactory, MacFactory) are often implemented as Singletons. This makes sense because you typically only need one instance of each factory to create all the products for that family.Connection, Command, and DataReader objects for different database vendors (e.g., a SqlServerFactory vs. an OracleFactory).