DEV Community

Gaurav
Gaurav

Posted on • Updated on

Decode Abstract Factory Design Pattern

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

When to use

  • To support families of related or dependent objects.
  • To encapsulate platform dependencies to make an application portable.
  • To prevent client code from using the 'new' operator.
  • To easily swap the underlying platform with minimal changes.

Intent

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Components

  • An Abstract Factory class (public)
  • Factory Implementations for various familes (protected)
  • Interfaces for various products (public)
  • Set of product implementations for various families (protected)

Structure

Image description

Implementation

1 Define interfaces for different types products/objects. Each family will have all these parts.

package com.gaurav.abstractfactory;

public interface Engine {

  public void design();
  public void manufacture();
  public void test();

}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.abstractfactory;

public interface Tyre {

  public void design();
  public void manufacture();

}
Enter fullscreen mode Exit fullscreen mode

2 Create sets of implementation subclasses for the above interfaces. Classes are access protected to prohibit instantiations in client modules using the 'new' operator.

package com.gaurav.abstractfactory;

class CarEngine implements Engine {

  @Override
  public void design() {
    System.out.println("Designing Car Engine");
  }

  @Override
  public void manufacture() {
    System.out.println("Manufacturing Car Engine");
  }

  @Override
  public void test() {
    System.out.println("Testing Car Engine");
  }

}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.abstractfactory;

class TruckEngine implements Engine {

  @Override
  public void design() {
    System.out.println("Designing Truck Engine");
  }

  @Override
  public void manufacture() {
    System.out.println("Manufacturing Truck Engine");
  }

  @Override
  public void test() {
    System.out.println("Testing Truck Engine");
  }

}
Enter fullscreen mode Exit fullscreen mode
package com.gaurav.abstractfactory;

class CarTyre implements Tyre {

  @Override
  public void design() {
    System.out.println("Designing Car Tyre");
  }

  @Override
  public void manufacture() {
    System.out.println("Manufacturing Car Tyre");
  }

}
Enter fullscreen mode Exit fullscreen mode

package com.gaurav.abstractfactory;

class TruckTyre implements Tyre {

  @Override
  public void design() {
    System.out.println("Designing Truck Tyre");
  }

  @Override
  public void manufacture() {
    System.out.println("Manufacturing Truck Tyre");
  }

}
Enter fullscreen mode Exit fullscreen mode

3 Create a Abstract Factory class with factory method 'getFactory()'. Clients can use this method to get an object the required factory. This example uses both Singleton and Factory Method patterns for better design.

package com.gaurav.abstractfactory;

public abstract class Factory {

  /* Singleton Factory objects */
  private static Factory carFactory = null;
  private static Factory truckFactory = null;

  public abstract Engine getEngine();
  public abstract Tyre getTyre();

  /*
   * This is the factory method exposed to the client.
   * Client requests for a factory instance by passing the type.
   * Client does not need to know about which & how
   * object is created internally.
   */
  public static Factory getFactory(String vehicleType)
      throws UnknownVehicleException {

    if (vehicleType == null) {
      return null;
    }

    Factory factory = null;
    switch (vehicleType) {
      case "car":
        if (carFactory == null)
          carFactory = new CarFactory();
        factory = carFactory;
        break;
      case "truck":
        if (truckFactory == null)
          truckFactory = new TruckFactory();
        factory = truckFactory;
        break;
      default:
        throw new UnknownVehicleException();
    }

    return factory;
  }
}
Enter fullscreen mode Exit fullscreen mode

4 Create Factory implementations. Classes are protected to prohibit direct access in client modules.

package com.gaurav.abstractfactory;

class CarFactory extends Factory {

  @Override
  public Engine getEngine() {
    return new CarEngine();
  }

  @Override
  public Tyre getTyre() {
    return new CarTyre();
  }

}
Enter fullscreen mode Exit fullscreen mode

package com.gaurav.abstractfactory;

public class TruckFactory extends Factory {

  TruckFactory() {}

  @Override
  public Engine getEngine() {
    return new TruckEngine();
  }

  @Override
  public Tyre getTyre() {
    return new TruckTyre();
  }

}
Enter fullscreen mode Exit fullscreen mode

5 The client code. Client is exposed to only the Abstract Factory class and the interfaces.

package com.gaurav.client;

import java.util.Scanner;

import com.gaurav.abstractfactory.Engine;
import com.gaurav.abstractfactory.Factory;
import com.gaurav.abstractfactory.Tyre;
import com.gaurav.abstractfactory.UnknownVehicleException;

public class AbstractFactoryClient {

  public static void main(String[] args) {

    Scanner in = new Scanner(System.in);
    String vehicleType = in.nextLine().toLowerCase();

    /* Get the factory instance */
    Factory factory;
    try {
      factory = Factory.getFactory(vehicleType);

      /* Get the Engine from the factory */
      Engine engine = factory.getEngine();
      engine.design();
      engine.manufacture();
      engine.test();

      /* Get the Tyre from the factory */
      Tyre tyre = factory.getTyre();
      tyre.design();
      tyre.manufacture();

    } catch (UnknownVehicleException e) {
      System.out.println("Invalid vehicle type entered!");
    }

    in.close();
  }

}
Enter fullscreen mode Exit fullscreen mode

Output

[input1]
    Car
[output1]
    Designing Car Engine
    Manufacturing Car Engine
    Testing Car Engine
    Designing Car Tyre
    Manufacturing Car Tyre

[input2]
    Bus
[output2]
    Invalid vehicle type entered!
Enter fullscreen mode Exit fullscreen mode

Benefits

  • Loosely coupled code.
  • Abstract Factory provides a single point of access for all products in a family.
  • New product family can be easily supported.

Drawbacks

  • More layers of abstraction increases complexity.
  • If there are any changes to any underlying detail of one factory, the interface might need to be modified for all the factories.

Real World Examples

Providing data access to two different data sources (e.g. a SQL Database and a XML file). You have two different data access classes (a gateway to the datastore). Both inherit from a base class that defines the common methods to be implemented (e.g. Load, Save, Delete). Which data source shall be used shouldn't change the way client code retrieves it's data access class. Your Abstract Factory knows which data source shall be used and returns an appropriate instance on request. The factory returns this instance as the base class type.

Software Examples

Dependency Injection

Java SDK Examples

javax.xml.parsers.DocumentBuilderFactory newInstance()
javax.xml.transform.TransformerFactory newInstance()
javax.xml.xpath.XPathFactory newInstance()

Hope you like it. Would love to hear your thoughts on this design pattern.

Lets have a Coffee

Top comments (3)

Collapse
 
srikanth597 profile image
srikanth597

I like it,
But For some reason I find it bit cumbersome if I want add new factory, I will have to add this to support in Factory class and add a switch case , or maybe some dependency injection can be done?

Collapse
 
muralikrishna8 profile image
Murali Krishna

Hey, CarEngine implementation is repeated instead of TruckEngine implementation.

Collapse
 
gauravratnawat profile image
Gaurav

Updated the blog. Thanks for pointing it out.