Abstract Factory Pattern

Definition

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

Introduction

In the last post – the Factory Method Pattern – we saw about the Factory method. This post will build on top of that and so if you haven’t read it, please do read it before this.

Quick recap:

Now, we have a TelevisionManufacturer that can manufacture various types of televisions. It has an abstract factory method to create a television. The TelevisionManufacturer is not coupled to any of the concrete television types. It is the responsibility of the subclass of TelevisionManufacturer to create concrete Television classes.

Let us explore and change the Television class and some of the concrete Television classes a little bit before moving on. Each television has a set of parts. For example, we have the main board, the buttons, special hardware features (USB, bluetooth connectivity) etc.

package com.javadevcentral.pattern.factory.televisions;

import java.util.List;

public class Television {
     String board;
     String button;
     List<String> specialFeatures;

    public void build() {
        System.out.println("Creating the main circuit board using " + board);
        System.out.println("Adding fancy buttons " + button);
        System.out.println("Adding features " + specialFeatures);
    }

    public void checkQuality() {
        System.out.println("Checking the Quality of the TV");
    }

    public void box() {
        System.out.println("Packing the TV in a box");
    }
}

A concrete television would initialize this during its construction.

package com.javadevcentral.pattern.factory.televisions;

import java.util.Arrays;
public class SamsungLCDTelevision extends Television {
    public SamsungLCDTelevision() {
        board = "Circuit board specific to Samsung";
        button = "Home grown Samsung buttons";
        specialFeatures = Arrays.asList("feature1" ,"feature2");
    }
}

Similar the other concrete televisions will be created. Since, we have added a build method to the Television, the TelevisionManufacturer invokes it as part of the creation process

package com.javadevcentral.pattern.factory.method;

import com.javadevcentral.pattern.factory.TelevisionType;
import com.javadevcentral.pattern.factory.televisions.Television;

public abstract class TelevisionManufacturer {
    public Television manufactureTelevision(TelevisionType televisionType) {
        Television television = createTelevision(televisionType);
        television.build(); 
        television.checkQuality();
        television.box();
        return television;
    }
    protected abstract Television createTelevision(TelevisionType televisionType);
}

Abstract Factory

Now, if you look at the individual concrete televisions, they are almost the same. The only difference is what television parts they initialize. Example: Samsung uses its own board, buttons and Philips has its own products too.

So, we will create an abstract factory that will create the individual parts. We will create subclasses of this for each of the Television manufacturers.

package com.javadevcentral.pattern.factory.abs;

import java.util.List;

public interface TelevisionPartsFactory {
    Board createBoard();
    Button createButton();
    List<Feature> createSpecialFeatures();
}
package com.javadevcentral.pattern.factory.abs;

import java.util.Arrays;
import java.util.List;

public class SamsungTelevisionPartsFactory implements TelevisionPartsFactory {
    @Override
    public Board createBoard() {
        return new SamsungTVBoard();
    }

    @Override
    public Button createButton() {
        return new SamsungButton();
    }

    @Override
    public List<Feature> createSpecialFeatures() {
        return Arrays.asList(new USBFeature());
    }
}

We have also created concrete parts classes SamsungBoard, SamsungButton, and USBFeature(not shown to avoid verbosity)

package com.javadevcentral.pattern.factory.televisions;

import java.util.List;
public abstract class Television {
    Board board;
    Button button;
    List<Feature> specialFeatures;

    public abstract void build(); //Have made it abstract

    public void checkQuality() {
        System.out.println("Checking the Quality of the TV");
    }

    public void box() {
        System.out.println("Packing the TV in a box");
    }
}

Also, note we have made Television an abstract class and have modeled each of the parts as having a common interface

With the parts factory, we don’t need different classes like SamsungLEDTelevision, PhilipsLEDTelevision. We just need one class for LEDTelevision and the parts to be initialized will depend on the concrete PartsFactory passed. Let us look at how this is done.

package com.javadevcentral.pattern.factory.abs;

public class LCDTelevision extends Television {
    private TelevisionPartsFactory televisionPartsFactory;

    public LCDTelevision(TelevisionPartsFactory televisionPartsFactory) {
        this.televisionPartsFactory = televisionPartsFactory;
    }

    @Override
    public void build() {
        board = televisionPartsFactory.createBoard();
        button = televisionPartsFactory.createButton();
        specialFeatures = televisionPartsFactory.createSpecialFeatures();
    }
}

SamsungTelevisionManufacturer now looks like

package com.javadevcentral.pattern.factory.abs;

import com.javadevcentral.pattern.factory.TelevisionType;
import com.javadevcentral.pattern.factory.method.TelevisionManufacturer;
import com.javadevcentral.pattern.factory.televisions.LEDTelevision;
import com.javadevcentral.pattern.factory.televisions.Television;

public class SamsungTelevisionManufacturer extends TelevisionManufacturer {
    @Override
    protected Television createTelevision(TelevisionType televisionType) {
        Television television;
        TelevisionPartsFactory televisionPartsFactory = new SamsungTelevisionPartsFactory();
        switch (televisionType) {
            case LED:
                television = new LEDTelevision(televisionPartsFactory);
                break;
            case LCD:
                television = new LCDTelevision(televisionPartsFactory);
                break;
            case PLASMA:
                television = new PlasmaTelevision(televisionPartsFactory);
                break;
            default:
                throw new RuntimeException("Unknown Television type " + televisionType);
        }
        return television;
    }
}

We create a SamsungTelevisionPartsFactory and pass it to the concrete television. It then uses this factory to create the parts.

televisionPartsFactory.createBoard()

If we had passed a Samsung Parts Factory, this will return the board for Samsung. If we had passed a Philips Parts Factory, this will return the board for Philips and so on.

We have provided a way of creating a family of products by a new type of factory called the Abstract factory.

Abstract factory provides interface for creating a family of products. It groups together related sets of products. By using this, our code will be decoupled from the actual factory that creates the products. We can create different factories that create products for different context – region, country or companies (as we saw here)

Note: The methods in the abstract factory are factory methods. This is how the factory method and the abstract factory are related.

Structure

Participants

AbstractFactory (TelevisionPartsFactory)

  • declares an interface for operations that create abstract product objects.

ConcreteFactory (SamsungTelevisionPartsFactory, PhilipsTelevisionPartsFactory)

  • implements the operations to create concrete product objects.

AbstractProduct (Board, Button, Feature)

– declares an interface for a type of product object.

ConcreteProduct (SamsungTVBoard, PhilipsTVBoard)

  • defines a product object to be created by the corresponding concrete factory.

Client

  • uses only interfaces declared by AbstractFactory and AbstractProduct classes.

Pros

  • Isolates concrete classes: The abstract factory encapsulates the responsibility and the process of creating product objects. It thus isolates the clients from the concrete classes. Clients depend only on the abstract interfaces. The concrete classes do not appear in the client code.
  • It makes exchanging product families easy: The concrete product classes appear only in the factories that create them. Hence it is easy to change the concrete factory that the client uses. We can get a totally different family of products by simply switching the concrete factory. With this, the whole product family changes at once. Because of this, it is also simple to add a new concrete factory implementation.
  • It promotes consistency among products: When product objects in a family are designed to work together, it’s important that an application use objects from only one family at a time. AbstractFactory makes this easy to enforce. In our example, it is not possible to use a Samsung board and a Philips button to manufacture a television.

Cons

  • Supporting new kinds of products is difficult: To add a new product type, we need to change the abstract factory. This implies that all the existing subclasses have to be changed.

Design principles used

Conclusion

The Abstract factory allows to create a family of related products. This abstracts the client from the creation of products. When a system should be configured to use one of multiple families of products, the Abstract Factory pattern is used. It enables to use the related products that belong to a family together.

References and Resources

  1. Head First Design Patterns: A Brain-Friendly Guide by Eric Freeman and Elisabeth Robson.
  2. Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
  3. Abstract Factory Wikipedia article

Leave a Reply