How do I implement the Abstract Factory pattern in Java?

A report exporter that supports CSV and TSV needs to make two decisions together. A CSV file should have comma-separated rows and a .csv extension. A TSV file should have tab-separated rows and a .tsv extension. If the exporter chooses these independently, it can give a tab-separated file a CSV name.

Abstract Factory is a Gang of Four creational pattern that groups the creation of related products behind a shared interface. In this example, the application chooses an export factory once and uses it to obtain a matching row formatter and file naming policy. Design Patterns: Abstract Factory

This article follows Factory Method in the creational patterns series, which began with Singleton.

A complete example

Save the following as AbstractFactoryExample.java. The example previews one row with two integer fields, so it can focus on how the factory selects the delimiter and file extension.

package org.kodejava.pattern.abstractfactory;

public class AbstractFactoryExample {
    interface RowFormatter {
        String format(int orderId, int quantity);
    }

    interface FileNaming {
        String fileName(String baseName);
    }

    interface ExportFactory {
        RowFormatter createRowFormatter();
        FileNaming createFileNaming();
    }

    static final class CsvRowFormatter implements RowFormatter {
        @Override
        public String format(int orderId, int quantity) {
            return orderId + "," + quantity;
        }
    }

    static final class CsvFileNaming implements FileNaming {
        @Override
        public String fileName(String baseName) {
            return baseName + ".csv";
        }
    }

    static final class CsvExportFactory implements ExportFactory {
        @Override
        public RowFormatter createRowFormatter() {
            return new CsvRowFormatter();
        }

        @Override
        public FileNaming createFileNaming() {
            return new CsvFileNaming();
        }
    }

    static final class TsvRowFormatter implements RowFormatter {
        @Override
        public String format(int orderId, int quantity) {
            return orderId + "\t" + quantity;
        }
    }

    static final class TsvFileNaming implements FileNaming {
        @Override
        public String fileName(String baseName) {
            return baseName + ".tsv";
        }
    }

    static final class TsvExportFactory implements ExportFactory {
        @Override
        public RowFormatter createRowFormatter() {
            return new TsvRowFormatter();
        }

        @Override
        public FileNaming createFileNaming() {
            return new TsvFileNaming();
        }
    }

    static void previewExport(ExportFactory factory) {
        RowFormatter formatter = factory.createRowFormatter();
        FileNaming naming = factory.createFileNaming();

        System.out.println(naming.fileName("orders"));
        System.out.println(formatter.format(1042, 3));
    }

    public static void main(String[] args) {
        previewExport(new CsvExportFactory());
        previewExport(new TsvExportFactory());
    }
}

Compile and run the example.

javac -d out AbstractFactoryExample.java
java -cp out org.kodejava.pattern.abstractfactory.AbstractFactoryExample

The first row uses a comma. The second uses a tab.

orders.csv
1042,3
orders.tsv
1042    3

How the products stay in sync

GoF role Java example Responsibility
Abstract factory ExportFactory Declares an operation for each product type.
Concrete factories CsvExportFactory, TsvExportFactory Supply the products for one export format.
Abstract products RowFormatter, FileNaming Define the operations the client uses.
Concrete products CsvRowFormatter, CsvFileNaming, TsvRowFormatter, TsvFileNaming Implement the delimiter and extension for their format.
Client previewExport() Uses both products from the factory it receives.

Follow the CSV call from main(). The client receives a CsvExportFactory through the ExportFactory interface. It asks the factory for both products, then calls them through their respective interfaces. The TSV call runs the same client code with a different factory. The matching pair comes from using the same factory for both requests. A factory implementation still has to honor that contract.

The previous article used Factory Method to let a subclass choose the product used by a shared notification workflow. Here, the exporter receives a factory object that supplies two related product types. The GoF describe factory methods as one way to implement the creation operations of an Abstract Factory. Design Patterns: Abstract Factory

When should I use it?

Use Abstract Factory when several components vary together and the client needs to switch among complete families. For example, a real exporter might pair a row formatter with a format-specific escaping policy and media type.

Adding a pipe-delimited family requires a new factory and its products. The previewExport() method can already use that factory through ExportFactory. Adding a new product type, such as a separate header formatter, means adding a creation method to ExportFactory and implementing it for every format. This structure fits applications where the set of product types is fairly stable.

For an exporter whose only differences are a delimiter and a file extension, one configuration object can hold both values. Separate product interfaces earn their place when each format supplies behavior that varies independently of those settings.

The next article covers Builder.

Leave a Comment.

This site uses Akismet to reduce spam. Learn how your comment data is processed.