Abstract Factory Pattern
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is particularly useful when a system needs to be independent of how its products are created, composed, and represented.
Consider a scenario where we're building a theme switcher for an application. When changing themes, multiple UI elements need to be updated consistently. For our example, we'll focus on two UI components that must be coordinated:
- Background
- Header
These components are interdependent - they need to match visually to create a cohesive user experience.
Code Implementation
First, let's define the interfaces for our products:
// Background interface (Product A)
public interface ThemeBackground {
public String getStyle();
}
// Header interface (Product B)
public interface ThemeHeader {
public String getStyle();
}
// Clean background style (Product A1)
public class CleanBackground implements ThemeBackground {
@Override
public String getStyle() {
return "Clean and minimal background style";
}
}
// Traditional Chinese background style (Product A2)
public class ChineseBackground implements ThemeBackground {
@Override
public String getStyle() {
return "Traditional Chinese background style";
}
}
// Clean header style (Product B1)
public class CleanHeader implements ThemeHeader {
@Override
public String getStyle() {
return "Clean and minimal header";
}
}
// Traditional Chinese header style (Product B2)
public class ChineseHeader implements ThemeHeader {
@Override
public String getStyle() {
return "Traditional Chinese header";
}
}
Next, let's define the factory interface and its implementations:
// Factory interface
public interface ThemeFactory {
public ThemeBackground createBackground();
public ThemeHeader createHeader();
}
// Clean theme factory
public class CleanThemeFactory implements ThemeFactory {
@Override
public ThemeBackground createBackground() {
ThemeBackground background = new CleanBackground();
System.out.println("Applied: " + background.getStyle());
return background;
}
@Override
public ThemeHeader createHeader() {
ThemeHeader header = new CleanHeader();
System.out.println("Applied: " + header.getStyle());
return header;
}
}
// Traditional Chinese theme factory
public class ChineseThemeFactory implements ThemeFactory {
@Override
public ThemeBackground createBackground() {
ThemeBackground background = new ChineseBackground();
System.out.println("Applied: " + background.getStyle());
return background;
}
@Override
public ThemeHeader createHeader() {
ThemeHeader header = new ChineseHeader();
System.out.println("Applied: " + header.getStyle());
return header;
}
}
Finally, let's see how a client would use these factories:
public class Application {
public static void main(String[] args) {
ThemeFactory factory = new CleanThemeFactory();
System.out.println("-------- Applying Clean Theme --------");
ThemeHeader header = factory.createHeader();
ThemeBackground background = factory.createBackground();
System.out.println("------- Clean Theme Applied -------");
System.out.println("\n-------- Applying Traditional Chinese Theme --------");
factory = new ChineseThemeFactory();
header = factory.createHeader();
background = factory.createBackground();
System.out.println("------- Traditional Chinese Theme Applied -------");
}
}
Pattern Analysis
The Abstract Factory pattern addresses the need to create families of related objects. In our example, each factory creates two objects that are thematically consistent. The key insight is that these objects are interdependent - using a clean header with a traditional Chinese background would create an inconsistent user experience.
If we need to add new themes (horizontal extension), we simply create new factory implementations. However, if we need to add new product types to our existing themes (vertical extension), we would need to modify the factory interfaces and all implementations.
Applicability
The Abstract Factory pattern is suitable in the following scenarios:
- When a system should be independent of how its products are created, composed, and represented.
- When a system should be configured with one of multiple families of products.
- When you want to provide a class library of products, and you want to reveal only their interfaces, not their implementations.
Comparison with Other Factory Patterns
The three factory patterns are related but serve different purposes:
- Simple Factory: The simplest approach that decouples object creation from client code but lacks flexibility for extension.
- Factory Method: Improves upon Simple Factory by using inheritance to allow extension without modifying existing code.
- Abstract Factory: Extends Factory Method by creating families of related objects, ensuring they're compatible with each other.