2.3. Abstract Factory

Maheshmaddi
4 min readApr 9, 2023

--

The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. The pattern allows developers to work with objects that belong to different families, while still maintaining a consistent interface and promoting loose coupling.

The Abstract Factory pattern is particularly useful when:

  1. The system needs to work with multiple families of related products, which may have different implementations.
  2. The products within a family are designed to work together, and their usage should be constrained to a specific family.

To implement the Abstract Factory pattern, follow these steps:

  1. Define common interfaces or abstract classes for each type of product in a family.
  2. Create concrete product classes that implement the common interfaces or extend the abstract classes.
  3. Declare an abstract factory interface or an abstract class, which contains methods for creating each type of product in a family.
  4. Implement concrete factory classes for each family that create and return the appropriate concrete product instances.

Here’s a simple example of the Abstract Factory pattern in Java:

// Common interfaces for products
public interface ProductA {
void doSomethingA();
}

public interface ProductB {
void doSomethingB();
}

// Concrete product classes
public class ConcreteProductA1 implements ProductA {
@Override
public void doSomethingA() {
System.out.println("ConcreteProductA1 does something");
}
}

public class ConcreteProductA2 implements ProductA {
@Override
public void doSomethingA() {
System.out.println("ConcreteProductA2 does something");
}
}

public class ConcreteProductB1 implements ProductB {
@Override
public void doSomethingB() {
System.out.println("ConcreteProductB1 does something");
}
}

public class ConcreteProductB2 implements ProductB {
@Override
public void doSomethingB() {
System.out.println("ConcreteProductB2 does something");
}
}

// Abstract factory interface
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}

// Concrete factory classes
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}

@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}

public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}

@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}

In this example, ProductA and ProductB interfaces define the common interfaces for two types of products. The concrete product classes (ConcreteProductA1, ConcreteProductA2, ConcreteProductB1, and ConcreteProductB2) implement these interfaces. The AbstractFactory interface contains methods for creating each type of product, and the concrete factory classes (ConcreteFactory1 and ConcreteFactory2) implement these methods to create and return the appropriate concrete product instances.

Advantages of the Abstract Factory pattern:

  1. Loose coupling: The pattern decouples client code from concrete product classes, allowing for easier addition or modification of product families without affecting client code.
  2. Consistency among products: The Abstract Factory pattern ensures that products within a family are used together, which can help maintain consistency and prevent errors caused by mixing incompatible products.
  3. Extensibility: The pattern allows for the easy addition of new product families or modifications to existing families, as long as they conform to the common interfaces or abstract classes.

Disadvantages of the Abstract Factory pattern:

  1. Increased complexity: The Abstract Factory pattern introduces additional classes and interfaces, which can increase the overall complexity of the code. It can be challenging to set up and manage a large number of product families and their dependencies.
  2. Difficulty in supporting new product types: If a new product type needs to be added to the existing product families, the abstract factory interface or abstract class must be updated to include the new product type. This change can affect all concrete factory classes and potentially the client code.

When using the Abstract Factory pattern, carefully consider its benefits and drawbacks. Use the pattern when you need to work with multiple families of related products, and you want to ensure that products within a family are used together while maintaining a consistent interface and promoting loose coupling. Be prepared to manage the complexity that arises from having multiple product families, and keep in mind the potential challenges when adding new product types.

Use case : Vehicle Manufacturing System

In a vehicle manufacturing system, we have different types of vehicles such as cars, motorcycles, and trucks. Each type of vehicle can have different variants, such as electric or gasoline-powered. We can use the Abstract Factory pattern to provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Here’s a Java code example demonstrating the use of the Abstract Factory pattern for this vehicle manufacturing system:

// Abstract Product
interface Vehicle {
void assemble();
}

// Concrete Products
class ElectricCar implements Vehicle {
@Override
public void assemble() {
System.out.println("Assembling electric car...");
}
}

class GasolineCar implements Vehicle {
@Override
public void assemble() {
System.out.println("Assembling gasoline car...");
}
}

class ElectricMotorcycle implements Vehicle {
@Override
public void assemble() {
System.out.println("Assembling electric motorcycle...");
}
}

class GasolineMotorcycle implements Vehicle {
@Override
public void assemble() {
System.out.println("Assembling gasoline motorcycle...");
}
}

// Abstract Factory
interface VehicleFactory {
Vehicle createVehicle();
}

// Concrete Factories
class ElectricCarFactory implements VehicleFactory {
@Override
public Vehicle createVehicle() {
return new ElectricCar();
}
}

class GasolineCarFactory implements VehicleFactory {
@Override
public Vehicle createVehicle() {
return new GasolineCar();
}
}

class ElectricMotorcycleFactory implements VehicleFactory {
@Override
public Vehicle createVehicle() {
return new ElectricMotorcycle();
}
}

class GasolineMotorcycleFactory implements VehicleFactory {
@Override
public Vehicle createVehicle() {
return new GasolineMotorcycle();
}
}

// Client
public class VehicleManufacturingSystem {
public static void main(String[] args) {
VehicleFactory electricCarFactory = new ElectricCarFactory();
VehicleFactory gasolineCarFactory = new GasolineCarFactory();
VehicleFactory electricMotorcycleFactory = new ElectricMotorcycleFactory();
VehicleFactory gasolineMotorcycleFactory = new GasolineMotorcycleFactory();

Vehicle electricCar = electricCarFactory.createVehicle();
Vehicle gasolineCar = gasolineCarFactory.createVehicle();
Vehicle electricMotorcycle = electricMotorcycleFactory.createVehicle();
Vehicle gasolineMotorcycle = gasolineMotorcycleFactory.createVehicle();

electricCar.assemble();
gasolineCar.assemble();
electricMotorcycle.assemble();
gasolineMotorcycle.assemble();
}
}

In this example, the Vehicle interface represents the abstract product. ElectricCar, GasolineCar, ElectricMotorcycle, and GasolineMotorcycle are concrete products implementing the Vehicle interface. VehicleFactory is the abstract factory, while ElectricCarFactory, GasolineCarFactory, ElectricMotorcycleFactory, and GasolineMotorcycleFactory are concrete factories implementing the VehicleFactory interface.

The client (VehicleManufacturingSystem) uses the concrete factories to create and assemble different types of vehicles without knowing their concrete implementation.

Note: For complete list of design patterns click here

--

--