Abstract Factory Pattern Code in Java
The Abstract Factory Pattern is a creational design pattern in Java that provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is particularly useful in scenarios where a system needs to be independent of how its objects are created, composed, and represented. For example, consider a GUI framework that supports multiple themes (e.g., Dark and Light themes). An abstract factory interface, such as GUIFactory, can define methods like createButton() and createCheckbox(). Concrete factories, like DarkThemeFactory and LightThemeFactory, implement this interface to produce theme-specific components (e.g., DarkButton, LightCheckbox). By using the abstract factory, the client code can work with the abstract GUIFactory interface and remain decoupled from the concrete implementations, enabling seamless theme switching at runtime.
To implement the Abstract Factory Pattern in Java, you start by defining an abstract factory interface or an abstract class that declares creation methods for each type of product. For instance, in a vehicle manufacturing system, you might have a VehicleFactory interface with methods like createCar() and createTruck(). Concrete factories, such as LuxuryVehicleFactory and EconomyVehicleFactory, implement this interface to create specific product variants, like LuxuryCar or EconomyTruck. The products themselves typically implement product-specific interfaces (e.g., Car and Truck) to ensure consistency. The client code interacts with the factory and product interfaces, allowing it to create and use objects without knowing their concrete classes. This abstraction promotes flexibility, as new factories and products can be added without modifying the client code, adhering to the Open/Closed Principle.
A practical Java example of the Abstract Factory Pattern can be seen in a cross-platform application that needs to create platform-specific UI components. Suppose you have an ApplicationFactory interface with methods createWindow() and createMenu(). Concrete factories like WindowsFactory and MacOSFactory implement this interface to produce WindowsWindow or MacOSMenu objects. The client code, such as an Application class, accepts a factory instance and uses it to create components: ApplicationFactory factory = new WindowsFactory(); Window window = factory.createWindow();. This approach ensures the application can run on different platforms by simply passing the appropriate factory at runtime. The pattern also enhances testability, as mock factories can be used in unit tests to simulate different environments, making the codebase more modular and maintainable.
If you need an additional level of abstraction over your Factory pattern than the Abstract Factory Pattern is the right design pattern to use. The Abstract Factory allows you to use different Factory implementations for different purposes. One of the best examples of the Abstract Factory and Factory pattern in Java is DocumentBuilderFactory and DocumentBuilder javax.xml.parsers package.
The following code is an example of the Abstract Factory Pattern.
// Factories
package com.java.creational.abstractfactory;
public abstract class FinancialToolsFactory {
public abstract TaxProcessor createTaxProcessor();
public abstract ShipFeeProcessor createShipFeeProcessor();
}
class CanadaFinancialToolsFactory extends FinancialToolsFactory {
public TaxProcessor createTaxProcessor() {
return new CanadaTaxProcessor();
}
public ShipFeeProcessor createShipFeeProcessor() {
return new CanadaShipFeeProcessor();
}
}
class EuropeFinancialToolsFactory extends FinancialToolsFactory {
public TaxProcessor createTaxProcessor() {
return new EuropeTaxProcessor();
}
public ShipFeeProcessor createShipFeeProcessor() {
return new EuropeShipFeeProcessor();
}
}
package com.java.creational.abstractfactory;
public abstract class ShipFeeProcessor {
abstract void calculateShipFee(Order order);
}
abstract class TaxProcessor {
abstract void calculateTaxes(Order order);
}
class EuropeShipFeeProcessor extends ShipFeeProcessor {
public void calculateShipFee(Order order) {
// insert here Europe specific ship fee calculation
}
}
class CanadaShipFeeProcessor extends ShipFeeProcessor {
public void calculateShipFee(Order order) {
// insert here Canada specific ship fee calculation
}
}
class EuropeTaxProcessor extends TaxProcessor {
public void calculateTaxes(Order order) {
// insert here Europe specific taxt calculation
}
}
class CanadaTaxProcessor extends TaxProcessor {
public void calculateTaxes(Order order) {
// insert here Canada specific taxt calculation
}
}
package com.java.creational.abstractfactory;
// Client
public class OrderProcessor {
private TaxProcessor taxProcessor;
private ShipFeeProcessor shipFeeProcessor;
public OrderProcessor(FinancialToolsFactory factory) {
taxProcessor = factory.createTaxProcessor();
shipFeeProcessor = factory.createShipFeeProcessor();
}
public void processOrder (Order order) {
// ....
taxProcessor.calculateTaxes(order);
shipFeeProcessor.calculateShipFee(order);
// ....
}
}
package com.java.creational.abstractfactory;
public class Application {
public static void main(String[] args) {
// .....
String countryCode = "EU";
Customer customer = new Customer();
Order order = new Order();
OrderProcessor orderProcessor = null;
FinancialToolsFactory factory = null;
if (countryCode == "EU") {
factory = new EuropeFinancialToolsFactory();
}
else if (countryCode == "CA") {
factory = new CanadaFinancialToolsFactory();
}
orderProcessor = new OrderProcessor(factory);
orderProcessor.processOrder(order);
}
}
// ----- Customer
package com.java.creational.abstractfactory;
import java.util.Date;
public class Customer {
private String firstName;
private String lastName;
private Date brithDate;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getBrithDate() {
return brithDate;
}
public void setBrithDate(Date brithDate) {
this.brithDate = brithDate;
}
}
// ------ Order
package com.java.creational.abstractfactory;
public class Order {
private char[] partNumber;
private String location;
private String cost;
}