Skip to content
On this page

Abstract Factory

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

The Abstract Factory pattern is a more complex creational pattern.

Unlike the Factory Method, the Abstract Factory pattern addresses more complex problems. Not only are the factories abstract, but the products are also abstract, and multiple products need to be created. Therefore, an Abstract Factory corresponds to multiple concrete factories, each responsible for creating multiple concrete products:

                                ┌────────┐
                             ─ ▶│ProductA│
    ┌────────┐    ┌─────────┐   │   └────────┘
    │ Client │─ ─▶│ Factory │─ ─
    └────────┘    └─────────┘   │   ┌────────┐
                       ▲         ─ ▶│ProductB│
               ┌───────┴───────┐    └────────┘
               │               │
          ┌─────────┐     ┌─────────┐
          │Factory1 │     │Factory2 │
          └─────────┘     └─────────┘
               │   ┌─────────┐ │   ┌─────────┐
                ─ ▶│ProductA1│  ─ ▶│ProductA2│
               │   └─────────┘ │   └─────────┘
                   ┌─────────┐     ┌─────────┐
               └ ─▶│ProductB1│ └ ─▶│ProductB2│
                   └─────────┘     └─────────┘

This pattern is somewhat similar to multiple suppliers providing a range of product types. Let's consider an example:

Suppose we want to provide users with a service that converts Markdown text to HTML and Word documents. The interface is defined as follows:

java
public interface AbstractFactory {
    // Create an HTML document:
    HtmlDocument createHtml(String md);
    // Create a Word document:
    WordDocument createWord(String md);
}

Notice that the above Abstract Factory is merely an interface without any implementation. Similarly, because HtmlDocument and WordDocument are both quite complex, we do not yet know how to implement them, so we only have interfaces:

java
// HtmlDocument interface:
public interface HtmlDocument {
    String toHtml();
    void save(Path path) throws IOException;
}

// WordDocument interface:
public interface WordDocument {
    void save(Path path) throws IOException;
}

Thus, we have defined the Abstract Factory (AbstractFactory) and two abstract products (HtmlDocument and WordDocument). Since implementing them is quite challenging, we decide to let the suppliers handle it.

Now, there are two suppliers in the market: FastDoc Soft offers inexpensive products with fast conversion speeds, while GoodDoc Soft offers expensive products with better conversion quality. We decide to use both suppliers' products to provide different services for free and paying users.

First, let's see how FastDoc Soft implements its products. FastDoc Soft must have concrete products, namely FastHtmlDocument and FastWordDocument:

java
public class FastHtmlDocument implements HtmlDocument {
    public FastHtmlDocument(String md) {
        // Initialize with Markdown content
        ...
    }

    public String toHtml() {
        // Convert Markdown to HTML quickly
        ...
    }

    public void save(Path path) throws IOException {
        // Save HTML document to the specified path
        ...
    }
}

public class FastWordDocument implements WordDocument {
    public FastWordDocument(String md) {
        // Initialize with Markdown content
        ...
    }

    public void save(Path path) throws IOException {
        // Save Word document to the specified path
        ...
    }
}

Next, FastDoc Soft must provide a concrete factory to produce these two types of products, namely FastFactory:

java
public class FastFactory implements AbstractFactory {
    public HtmlDocument createHtml(String md) {
        return new FastHtmlDocument(md);
    }

    public WordDocument createWord(String md) {
        return new FastWordDocument(md);
    }
}

With this setup, we can use FastDoc Soft's services. The client code is as follows:

java
// Create an AbstractFactory, actual type is FastFactory:
AbstractFactory factory = new FastFactory();
// Generate an HTML document:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "fast.html"));
// Generate a Word document:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "fast.doc"));

If we want to use GoodDoc Soft's services simultaneously, what should we do? Since we are using the Abstract Factory pattern, GoodDoc Soft only needs to implement their own concrete factory and concrete products based on our defined Abstract Factory and abstract product interfaces:

java
// Concrete Factory:
public class GoodFactory implements AbstractFactory {
    public HtmlDocument createHtml(String md) {
        return new GoodHtmlDocument(md);
    }

    public WordDocument createWord(String md) {
        return new GoodWordDocument(md);
    }
}

// Concrete Products:
public class GoodHtmlDocument implements HtmlDocument {
    public GoodHtmlDocument(String md) {
        // Initialize with Markdown content
        ...
    }

    public String toHtml() {
        // Convert Markdown to HTML with better quality
        ...
    }

    public void save(Path path) throws IOException {
        // Save HTML document to the specified path
        ...
    }
}

public class GoodWordDocument implements WordDocument {
    public GoodWordDocument(String md) {
        // Initialize with Markdown content
        ...
    }

    public void save(Path path) throws IOException {
        // Save Word document to the specified path
        ...
    }
}

To use GoodDoc Soft's services, the client only needs to switch from new FastFactory() to new GoodFactory():

java
// Create an AbstractFactory, actual type is GoodFactory:
AbstractFactory factory = new GoodFactory();
// Generate an HTML document:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "good.html"));
// Generate a Word document:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "good.doc"));

Notice that aside from using new FastFactory() or new GoodFactory(), the client code only references the product interfaces and does not reference any concrete products (e.g., FastHtmlDocument). If the factory creation code is placed within the AbstractFactory, the actual factories can also be hidden:

java
public interface AbstractFactory {
    // Creation methods:
    HtmlDocument createHtml(String md);
    WordDocument createWord(String md);

    // Get factory instance:
    static AbstractFactory createFactory(String name) {
        if (name.equalsIgnoreCase("fast")) {
            return new FastFactory();
        } else if (name.equalsIgnoreCase("good")) {
            return new GoodFactory();
        } else {
            throw new IllegalArgumentException("Invalid factory name");
        }
    }
}

Note: For the sake of simplifying the code, we only support two Markdown syntaxes: titles starting with # and regular body text.

Practice

Use the Abstract Factory pattern to implement HtmlDocument and WordDocument.

java
public interface AbstractFactory {
    HtmlDocument createHtml(String md);
    WordDocument createWord(String md);

    static AbstractFactory createFactory(String name) {
        if (name.equalsIgnoreCase("fast")) {
            return new FastFactory();
        } else if (name.equalsIgnoreCase("good")) {
            return new GoodFactory();
        } else {
            throw new IllegalArgumentException("Invalid factory name");
        }
    }
}

public interface HtmlDocument {
    String toHtml();
    void save(Path path) throws IOException;
}

public interface WordDocument {
    void save(Path path) throws IOException;
}

public class FastHtmlDocument implements HtmlDocument {
    public FastHtmlDocument(String md) {
        // Initialize with Markdown content
    }

    public String toHtml() {
        // Convert Markdown to HTML quickly
        return "<html>Fast HTML Content</html>";
    }

    public void save(Path path) throws IOException {
        // Save HTML document to the specified path
    }
}

public class FastWordDocument implements WordDocument {
    public FastWordDocument(String md) {
        // Initialize with Markdown content
    }

    public void save(Path path) throws IOException {
        // Save Word document to the specified path
    }
}

public class FastFactory implements AbstractFactory {
    public HtmlDocument createHtml(String md) {
        return new FastHtmlDocument(md);
    }

    public WordDocument createWord(String md) {
        return new FastWordDocument(md);
    }
}

public class GoodHtmlDocument implements HtmlDocument {
    public GoodHtmlDocument(String md) {
        // Initialize with Markdown content
    }

    public String toHtml() {
        // Convert Markdown to HTML with better quality
        return "<html>Good HTML Content</html>";
    }

    public void save(Path path) throws IOException {
        // Save HTML document to the specified path
    }
}

public class GoodWordDocument implements WordDocument {
    public GoodWordDocument(String md) {
        // Initialize with Markdown content
    }

    public void save(Path path) throws IOException {
        // Save Word document to the specified path
    }
}

public class Client {
    public static void main(String[] args) throws IOException {
        // Create AbstractFactory using the factory name:
        AbstractFactory factory = AbstractFactory.createFactory("fast");
        // Generate HTML document:
        HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
        html.save(Paths.get(".", "fast.html"));
        // Generate Word document:
        WordDocument word = factory.createWord("#Hello\nHello, world!");
        word.save(Paths.get(".", "fast.doc"));
        
        // Switch to GoodFactory:
        AbstractFactory goodFactory = AbstractFactory.createFactory("good");
        HtmlDocument goodHtml = goodFactory.createHtml("#Hello\nHello, world!");
        goodHtml.save(Paths.get(".", "good.html"));
        WordDocument goodWord = goodFactory.createWord("#Hello\nHello, world!");
        goodWord.save(Paths.get(".", "good.doc"));
    }
}

Summary

The Abstract Factory pattern aims to separate the creation of factories and a set of products from their usage, allowing for easy switching between different factories and their corresponding product sets.

Key points of implementing the Abstract Factory pattern include:

  • Define Factory and Product Interfaces: Define interfaces for factories (AbstractFactory) and products (HtmlDocument and WordDocument), but defer the implementation of these factories and products to concrete subclasses.

  • Defer Creation to Subclasses: Allow concrete subclasses (FastFactory, GoodFactory) to handle the creation of concrete products, ensuring that the client interacts only with the abstract interfaces.

  • Promote Programming to Abstractions: Clients should hold references to interfaces or abstract classes rather than concrete implementations, enabling the factory methods to switch between different subclasses without affecting the client's code.

In practice, the simpler Static Factory Method is more commonly used, allowing internal optimizations in product creation.

Clients should always reference interfaces rather than implementation classes. This allows subclasses to change without affecting the client's code, promoting programming to abstractions wherever possible.

Abstract Factory has loaded