Abstract Factory Pattern

Abstract factory is a factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.

abstract factory design pattern sequence diagram

Abstract factory pattern is yet another creational design pattern and is considered another layer of abstraction over factory pattern. The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.

In plain words:

Abstract factory is a factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.

In this tutorial, we will expand the scope of the car factory problem discussed in the factory pattern. We will learn when to use abstract factory patterns by broadening the scope of the car factory and then how abstract factory pattern solves the expanded scope.

1. Design global car factory using abstract factory pattern

In the factory pattern, we discussed how to abstract the car-making process for various car model types and their additional logic included in the car-making process. Now, imagine if our car maker decides to go global.

To support global operations, we will need to enhance the system to support different car-making styles in different countries. For example, in some countries, the steering wheel is on the left side, and in some countries, it is on the right side. There can be many more such differences in different parts of cars and their making processes.

To describe the abstract factory pattern, we will consider three kinds of makes—the USA, Asia, and the default for all other countries. Supporting multiple locations will require critical design changes.

First, we need car factories in each location specified in the problem statement, i.e., USACarFactory, AsiaCarFactory, and DefaultCarFactory. Now, our application should be smart enough to identify the location where it is being used, so we should be able to use the appropriate car factory without even knowing which factory implementation will be used internally. This also saves us from someone calling the wrong factory for a particular location.

So, we need another layer of abstraction that will identify the client location and internally use the correct car factory implementation without giving the user a single hint. This is exactly the problem that the abstract factory pattern solves.

2. Abstract factory pattern based solution

2.1. Package Diagram

Class diagram for participating classes in the design of global car factories using the abstract factory pattern.

abstract_fctory_package_diagram-9778485

2.2. Sequence Diagram

This diagram shows the interaction between classes and abstraction behind CarFactory factory class.

abstract_factory_sequence_diagram-9375997

Please note that I designed the solution to completely hide the location details from the end user. So, I have not exposed any location-specific factory directly.

In an alternate solution, we can first get the location-specific factory based on the location argument and then use its buildCar() method on abstract reference to build the actual car instance.

3. Abstract factory pattern implementation

Java classes implementing abstract factory patterns for global car factories.

First, wee have to write all separate car factories for different locations. To support location-specific features, begin by modifying our Car.java class with another attribute – location.

public abstract class Car {
 
  public Car(CarType model, Location location){
    this.model = model;
    this.location = location;
  }
 
  protected abstract void construct();
 
  private CarType model = null;
  private Location location = null;
 
  //getters and setters
 
  @Override
  public String toString() {
    return "Model- "+model + " built in "+location;
  }
}

This adds extra work of creating another enum for storing different locations.

public enum Location {
  DEFAULT, USA, ASIA
}

All car types will also have additional location property. We are writing only for the luxury car. The same follows for small and sedans also.

public class LuxuryCar extends Car {

  public LuxuryCar(Location location) {
    super(CarType.LUXURY, location);
    construct();
  }
 
  @Override
  protected void construct() {
    System.out.println("Building luxury car");
    //add accessories
  }
}

So far, we have created basic classes. Now, let’s have different car factories, which is the core idea behind the abstract factory pattern.

public class AsiaCarFactory {

  public static Car buildCar(CarType model) {

    Car car = null;
    switch (model) {

      case SMALL:
      car = new SmallCar(Location.ASIA);
      break;
 
      case SEDAN:
      car = new SedanCar(Location.ASIA);
      break;
 
      case LUXURY:
      car = new LuxuryCar(Location.ASIA);
      break;
 
      default:
      //throw some exception
      break;
    }
    return car;
  }
}
public class DefaultCarFactory {

  public static Car buildCar(CarType model) {

    Car car = null;

    switch (model) {
      case SMALL:
      car = new SmallCar(Location.DEFAULT);
      break;
 
      case SEDAN:
      car = new SedanCar(Location.DEFAULT);
      break;
 
      case LUXURY:
      car = new LuxuryCar(Location.DEFAULT);
      break;
 
      default:
      //throw some exception
      break;
    }
    return car;
  }
}
public class USACarFactory {

  public static Car buildCar(CarType model) {

    Car car = null;
    switch (model) {
      case SMALL:
      car = new SmallCar(Location.USA);
      break;
 
      case SEDAN:
      car = new SedanCar(Location.USA);
      break;
 
      case LUXURY:
      car = new LuxuryCar(Location.USA);
      break;
 
      default:
      //throw some exception
      break;
    }
  return car;
  }
}

Well, now we have all three different Car factories. We have to abstract the way these factories are accessed.

public class CarFactory {

  private CarFactory() {
    //Prevent instantiation
  }
 
  public static Car buildCar(CarType type) {

    Car car = null;
    Location location = Location.ASIA; //Read location property somewhere from configuration
    //Use location specific car factory

    switch(location) {
      case USA:
        car = USACarFactory.buildCar(type);
        break;
      case ASIA:
        car = AsiaCarFactory.buildCar(type);
        break;
      default:
        car = DefaultCarFactory.buildCar(type);
    }
  return car;
  }
}

4. Demo

We are done with writing code. Now, let’s test the factories and cars.

public class TestFactoryPattern
{
  public static void main(String[] args)
  {
    System.out.println(CarFactory.buildCar(CarType.SMALL));
    System.out.println(CarFactory.buildCar(CarType.SEDAN));
    System.out.println(CarFactory.buildCar(CarType.LUXURY));
  }
}

Program output:

Output: (Default location is Asia)
 
Building small car
Model- SMALL built in ASIA
 
Building sedan car
Model- SEDAN built in ASIA
 
Building luxury car
Model- LUXURY built in ASIA

5. Summary

We have already seen the use case scenarios of the Factory pattern, so whenever you need another level of abstraction over a group of factories, you should consider using the abstract factory pattern. It is probably the only difference between a factory pattern and an abstract factory pattern.

You can already look deeper into different real-life examples of abstract factories in JDK distribution:

There are other similar examples, but the need is to have the feel of the abstract factory design pattern, which you must have achieved until now.

Happy Learning!!

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
16 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.