Skip to content

Abstract Factory

The Abstract Factory pattern extends the idea of the Factory Method pattern and adds an additional level of abstraction to it. Like Factory Method, this pattern is used to create objects of a certain family (i.e. implementing a specific interface or inheriting a specific class). The difference is that we can divide these objects into certain groups. Then, based on these groups and the choice of the user we are able to create a factory that produces objects of a certain family belonging to a certain group. This means that there are many factories that create objects of the same type.

This pattern is best understood with an example. The following example shows how to build objects that extend the Car interface. In the example we find 6 implementations of this interface:

  • AudiA4Combi
  • AudiA4Sedan
  • AudiA4Hatchback
  • ToyotaCorollaCombi
  • ToyotaCorollaSedan
  • ToyotaCorollaHatchback

The first 3 implementations will go to the AudiA4 group, the next three to theToyotaCorolla group.

In turn, the factory (the CarFactory interface) will be able to produce an object of typeCar based on its body, i.e. Sedan, Combi or Hatchback. The example includes two implementations of such factories. These are the classes ToyotaCorollaFactory andAudiA4Factory.

The goal is to give the user a choice of a specific factory implementation. The FactoryProvider class is used for this. The choice is based on the input type. Due to the fact that all classes representing cars implement the Car interface and all factories implement the CarFactory interface, in a class with a main method, i.e. AbstractFactoryUsage, we can see that the use of these classes can be based entirely on abstractions.

Common interface for created objects:

public interface Car {
  Type getType();
  String getModelName();
  Integer getCylindersNum();
  String getProducer();
  Float getEngineVolume();
  Integer getTrunkSize();
}

public abstract class AbstractCar implements Car {
  @Override
  public String toString() {
    return "Car: " + getProducer() + " " + getModelName() + " " + getType() + " has " + getCylindersNum() + " cylinders" +
      " and engine: " + getEngineVolume() + " and trunk with size " + getTrunkSize() + " litres";
  }
}

Base class of all Toyota Corolla cars:

public abstract class ToyotaCorolla extends AbstractCar {
  @Override
  public String getModelName() {
    return "Corolla";
  }

  @Override
  public String getProducer() {
    return "Toyota";
  }
}

Implementations of ToyotaCorolla classes, i.e. the first group of objects from the Car family:

public class ToyotaCorollaCombi extends ToyotaCorolla {

  @Override
  public Type getType() {
    return Type.COMBI;
  }

  @Override
  public Integer getCylindersNum() {
    return 4;
  }

  @Override
  public Float getEngineVolume() {
    return 2.0F;
  }

  @Override
  public Integer getTrunkSize() {
    return 540;
  }
}

public class ToyotaCorollaHatchback extends ToyotaCorolla {
  @Override
  public Type getType() {
    return Type.HATCHBACK;
  }

  @Override
  public Integer getCylindersNum() {
    return 4;
  }

  @Override
  public Float getEngineVolume() {
    return 2.0F;
  }

  @Override
  public Integer getTrunkSize() {
    return 420;
  }
}
public class ToyotaCorollaSedan extends ToyotaCorolla {
  @Override
  public Type getType() {
    return Type.SEDAN;
  }

  @Override
  public Integer getCylindersNum() {
    return 4;
  }

  @Override
  public Float getEngineVolume() {
    return 1.8F;
  }

  @Override
  public Integer getTrunkSize() {
    return 480;
  }
}

Base class for all AudiA4 cars:

public abstract class AudiA4 extends AbstractCar {
  @Override
  public String getModelName() {
    return "A4";
  }

  @Override
  public String getProducer() {
    return "Audi";
  }
}
Implementations of AudiA4 classes, i.e. the second group of objects from the Car family:
public class AudiA4Combi extends AudiA4 {

  @Override
  public Type getType() {
    return Type.COMBI;
  }

  @Override
  public Integer getCylindersNum() {
    return 6;
  }

  @Override
  public Float getEngineVolume() {
    return 2.7F;
  }

  @Override
  public Integer getTrunkSize() {
    return 540;
  }
}
public class AudiA4Hatchback extends AudiA4 {
  @Override
  public Type getType() {
    // there is no a4 hatchback but its only an example ;)
    return Type.HATCHBACK;
  }

  @Override
  public Integer getCylindersNum() {
    return 4;
  }

  @Override
  public Float getEngineVolume() {
    return 1.9F;
  }

  @Override
  public Integer getTrunkSize() {
    return 340;
  }
}
public class AudiA4Sedan extends AudiA4 {
  @Override
  public Type getType() {
    return Type.SEDAN;
  }

  @Override
  public Integer getCylindersNum() {
    return 6;
  }

  @Override
  public Float getEngineVolume() {
    return 2.5F;
  }

  @Override
  public Integer getTrunkSize() {
    return 460;
  }
}

An abstraction representing the common interface of all factories:

public interface CarFactory {
  Car createSedan();
  Car createCombi();
  Car createHatchback();
}

Factory implementations:

public class ToyotaCorollaFactory implements CarFactory {

  @Override
  public Car createSedan() {
    return new ToyotaCorollaSedan();
  }

  @Override
  public Car createCombi() {
    return new ToyotaCorollaCombi();
  }

  @Override
  public Car createHatchback() {
    return new ToyotaCorollaHatchback();
  }
}

public class AudiA4Factory implements CarFactory {

  @Override
  public Car createSedan() {
    return new AudiA4Sedan();
  }

  @Override
  public Car createCombi() {
    return new AudiA4Combi();
  }

  @Override
  public Car createHatchback() {
    return new AudiA4Hatchback();
  }
}

An additional level of abstraction in the described pattern - objects that allow the selection of a specific factory.

public enum  CarType {
  TOYOTA_COROLLA,
  AUDI_A4
}

public class FactoryProvider {
  public CarFactory createFactory(final CarType carType) {
    switch (carType) {
      case AUDI_A4:
        return new AudiA4Factory();
      case TOYOTA_COROLLA:
        return new ToyotaCorollaFactory();
    }
    throw new UnsupportedOperationException("Cannot produce factory for this car type");
  }
}

Exemplary use of the pattern:

public class AbstractFactoryUsage {
  public static void main(String[] args) {
    CarType carType = CarType.valueOf(args[0]); // np. AUDI_A4
    System.out.println("User wants to produce a " + carType); // User wants to produce a AUDI_A4

    CarFactory factory = new FactoryProvider().createFactory(carType);
    Car combi = factory.createCombi();

    System.out.println("There is your combi " + combi); // output: There is your combi Car: Audi A4 COMBI has 6 cylinders and engine: 2.7 and trunk with size 540 litres
  }
}

As you can see, the main advantage of the Abstract Factory design pattern is the ability to work with abstractions, not breaking SOLID rules and hiding implementation details. The problem may be a large number of classes and interfaces that we have to implement to use it.