http://www.developer.com/

Back to article

Working With Design Patterns: Abstract Factory


September 4, 2008

Like many design patterns, the abstract factory pattern and the builder pattern appear very similar in terms of their class structure. They are both creational patterns used to construct complex object families. However, the builder pattern has an emphasis on step-by-step construction of each part of the composite structure, whereas the abstract factory is designed to return a constructed object immediately.

Client code plays different roles in the two pattern implementations. Generally, when using the builder pattern, the client knows the component types to be passed into the builder. With an abstract factory, the client requests an implementation of an interface, and has no idea about the specific implementation types of the returned object structure.

To best contrast the differences between abstract factory and builder, I'll use the same example that I presented in my article on the builder pattern: In a library, a catalog is a collection of various materials—books and movies (see Listing 1). Libraries print various reports on a frequent basis, including a rather large inventory report that lists all materials in the library, sorted first by material type. These reports can go to either the web or to a simple text-based desktop user interface. (Reuse is such a great thing that I've even been able to reuse a bit of text here!)

Listing 1: Basic materials.

// Material.java:
public class Material {
   private String classification;
   private String title;
   private String director;
   private String year;

   public Material(String classification, String title,
                   String director, String year) {
      this.classification = classification;
      this.title = title;
      this.director = director;
      this.year = year;
   }

   public String getAuthor() {
      return director;
   }

   public String getClassification() {
      return classification;
   }

   public String getTitle() {
      return title;
   }

   public String getYear() {
      return year;
   }
}

// Book.java:
public class Book extends Material {
   public Book(String classification, String title,
               String author, String year) {
      super(classification, title, author, year);
   }
   // ...
}

// Movie.java:
public class Movie extends Material {
   public Movie(String classification, String title,
                String director, String year) {
      super(classification, title, director, year);
   }

   public String getDirector() {
      return getAuthor();
   }
   // ...
}

The Catalog class is a simple container of materials. See Listing 2.

Listing 2: The Catalog class.

import java.util.*;

public class Catalog implements Iterable<Material> {
   private List<Material> materials = new ArrayList<Material>();

   public void add(Material material) {
      materials.add(material);
   }

   public List<Material> materials() {
      return materials;
   }

   @Override
   public Iterator<Material> iterator() {
      return materials.iterator();
   }
}

The goal of a reporting subsystem is to take a client interest in platform (web or desktop), plus the list of materials, and produce a report appropriate to the platform. The client specifies the platform using an enum (see Listing 3). The implementation this time around will use the abstract factory pattern. As the builder article states, the steps to construct a report are simple: Print a header, iterate through a sorted list of the materials and print a row for each, and finally print a summary section.

Listing 3: The Platform enum.

public enum Platform {
   WEB, DESKTOP
}

Because the result of applying the abstract factory pattern to create a report should be the same as applying the builder pattern, I built a simple integration test to compare the two reports. The tests in Listing 4 show how clients interact with both the abstract factory and the builder to produce reports.

Listing 4: An integration test.

import static org.junit.Assert.*;
import org.junit.*;
import builder.*;
import domain.*;

public class FactoryTest {
   private Catalog catalog;

   @Before
   public void initialize() {
      catalog = createCatalog();
   }

   @Test
   public void compareWebReports() {
      CatalogReportFactory factory =
         CatalogReportFactory.create(catalog, Platform.WEB);
      CatalogReportDirector director =
         new CatalogReportDirector(catalog);

      assertEquals(director.generate(new HtmlCatalogBuilder()),
                   factory.generate());
   }

   @Test
   public void compareDesktopReports() {
      CatalogReportFactory factory =
         CatalogReportFactory.create(catalog, Platform.DESKTOP);
      CatalogReportDirector director =
         new CatalogReportDirector(catalog);

      assertEquals(director.generate(new PrintCatalogBuilder()),
                   factory.generate());
   }

   private Catalog createCatalog() {
      Catalog catalog = new Catalog();
      catalog.add(new Book("QA234.543.34", "Java Puzzlers",
                           "Bloch, Joshua", "2006"));
      catalog.add(new Book("QA234.543.33", "Agile Java",
                           "Langr, Jeff", "2005"));
      catalog.add(new Movie("ZZ234", "Fight Club",
                            "Fincher, David", "1999"));
      catalog.add(new Book("QA234.543.35", "Clean Code",
                           "Martin, Robert C.", "2008"));
      catalog.add(new Movie("ZZ2888", "A Clockwork Orange",
                            "Kubrick, Stanley", "1971"));
      catalog.add(new Book("QA234.543.399",
                           "Working Effectively With Legacy Code",
                           "Feathers, Michael", "2004"));
      catalog.add(new Book("QA234.543.452", "Secrets of Consulting",
                           "Weinberg, Gerald", "1995"));
      catalog.add(new Movie("ZZ234", "Kill Bill: Vol. 1",
                            "Tarantino, Quentin", "2003"));
      return catalog;
   }
}

The static create method defined on CatalogReportFactory is in itself a factory method. Its job is to return a factory specific to the request of the client. This routing of a client request to an appropriate factory is the heart of the abstract factory pattern. Listing 5 shows the CatalogReportFactory implementation.

Listing 5: CatalogReportFactory.

import java.util.*;
import domain.*;

public abstract class CatalogReportFactory {
   protected final Catalog catalog;

   public static CatalogReportFactory create(Catalog catalog,
                                             Platform platform) {
      if (platform == Platform.WEB)
         return new HtmlCatalogReportFactory(catalog);
      return new PrintCatalogReportFactory(catalog);
   }

   public CatalogReportFactory(Catalog catalog) {
      this.catalog = catalog;
   }

   public String generate() {
      Report report = new Report();
      createReportHeader().appendTo(report);
      for (Material material: sortedCatalog())
         createDetailRecord(material).appendTo(report);
      createSummary().appendTo(report);
      return report.toString();
   }

   private List<Material> sortedCatalog() {
      return new MaterialSorter(catalog.materials()).sort();
   }

   abstract protected ReportComponent createSummary();
   abstract protected ReportComponent
      createDetailRecord(Material material);
   abstract protected ReportComponent createReportHeader();
}

The abstract methods defined on CatalogReportFactory are placeholders for a family of related factories. Each CatalogReportFactory implementation is required to provide factory implementations that can produce headers, detail records, and summaries. The factory implementation that produces HTML reports appears in Listing 6. (In case you're interested, the MaterialSorter code appears in Listing 7.)

Listing 6: HtmlCatalogReportFactory.

import domain.*;

public class HtmlCatalogReportFactory extends CatalogReportFactory {
   public HtmlCatalogReportFactory(Catalog catalog) {
      super(catalog);
   }

   @Override
   protected ReportComponent createDetailRecord(Material material) {
      return new HtmlReportDetailRecord(material);
   }

   @Override
   protected ReportComponent createReportHeader() {
      return new HtmlReportHeader();
   }

   @Override
   protected ReportComponent createSummary() {
      return new HtmlReportSummary();
   }
}

Listing 7: MaterialSorter.

import java.util.*;
import domain.*;

public class MaterialSorter implements Comparator<Material> {
   private List<Material> materialsCopy;

   public MaterialSorter(List<Material> materials) {
      materialsCopy = new ArrayList<Material>(materials);
   }

   @Override
   public int compare(Material material1, Material material2) {
      if (material1.getClass() == material2.getClass()) {
         if (material1.getTitle().equals(material2.getTitle()))
            return material1.getAuthor().
               compareTo(material2.getAuthor());
         return material1.getTitle().
            compareTo(material2.getTitle());
      }
      return material1.getClass().getName().
         compareTo(material2.getClass().getName());
   }

   public List<Material> sort() {
      Collections.sort(materialsCopy, this);
      return materialsCopy;
   }
}

Each creation method returns a ReportComponent:

public interface ReportComponent {
   void appendTo(Report report);
}

The job of a ReportComponent is to produce some amount of text that can be appended to a Report. A Report is a simple helper class—basically a wrapper for StringBuilder—that adds a few useful elements to help simplify client code:

public class Report {
   static final String EOL = System.getProperty("line.separator");
   private StringBuilder buffer = new StringBuilder();

   @Override
   public String toString() {
      return buffer.toString();
   }

   public void appendLine(String text) {
      buffer.append(text);
      buffer.append(EOL);
   }
}

Each of the HTML report component factories is, for the most part, a trivial one-line method. The three implementations, plus the Html utility class, appear in Listing 8.

Listing 8: HTML factory components.

// Html.java
public class Html {
   public static String td(String element) {
      return tag(element, "td");
   }

   public static String th(String element) {
      return tag(element, "th");
   }

   public static String tag(String element, String tag) {
      return String.format("<%s>%s</%s>", tag, element, tag);
   }
}

// HtmlReportHeader.java
import static abstractfactory.Html.*;

public class HtmlReportHeader implements ReportComponent {
   @Override
   public void appendTo(Report report) {
      report.appendLine("<body>");
      report.appendLine("<table>");
      report.appendLine(String.format("<tr>%s%s%s%s</tr>",
                        th("Title"), th("Auth/Dir"),
                        th("Classification"), th("Year")));

   }
}

// HtmlReportDetailRecord.java
import static abstractfactory.Html.*;
import domain.*;

public class HtmlReportDetailRecord implements ReportComponent {
   private final Material material;

   public HtmlReportDetailRecord(Material material) {
      this.material = material;
   }

   @Override
   public void appendTo(Report report) {
      report.appendLine(String.format("<tr>%s%s%s%s</tr>",
         td(material.getTitle()), td(material
         .getAuthor()), td(material.getClassification()),
         td(material.getYear())));
   }
}


// HtmlReportSummary.java
public class HtmlReportSummary implements ReportComponent {
   @Override
   public void appendTo(Report report) {
      report.appendLine("</table>");
      report.appendLine("</body>");
   }
}

Each of these ReportComponent implementations is also trivial to test! Further, they provide ultimate flexibility as the kinds of reports grow. The HtmlReportSummary implementation, for example, could be used for any sort of HTML report.

Is this an appropriate use of the abstract factory pattern, and a better choice than builder? That's for you to decide. In this case, perhaps. It fulfills the interest of hiding the type information from a client. The client still must pass in an indicator that specifies whether or not the report is a platform or web report. That information could have as easily been buried in a properties file read by the abstract factory itself. The builder, on the other hand, is designed to highlight step-by-step construction, but that's not really necessary for this use.

As with all design patterns, abstract factory has some deep similarities to another pattern. The challenge is applying the pattern that best fits your needs. In many cases, you'll find yourself building a hybrid implementation.



Click here for a larger image.

Figure 1: The abstract factory pattern.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff contributed to Uncle Bob Martin's new book, Clean Code (Prentice Hall, August 2008). Jeff has written over 80 articles on software development, with over thirty-five appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date