There are times when you can use a <a href="https://www.devmaking.com/learn/design-patterns/factory-pattern/" target="_blank" style="color:inherit">factory pattern</a> to help create on the fly, providing an interface for composing objects. One such example is rendering GUI on a screen; using a local operating system's UI library can help a developer make sleek, native applications. When paired with the factory pattern, the UI can be pre-assembled and modularized for the specific project, i.e., composing a graph widget from multiple UI components.

However, what if the developer wanted to deploy on a second operating system with its own UI library? Instead of recreating the application specifically for that OS, you could make an abstract factory.

Assumptions

What is an Abstract Factory?

An abstract factory is a creational design pattern that adds a layer of abstraction to the factory pattern, and is sometimes thought of as being a "factory that creates factories". Additionally, the abstract factory pattern is usually designated for creating "families" of related items.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/AbstractFactory_01.png" alt="abstract factory high level." style="max-width:95%;"> </div>

By creating a layer of abstraction between the concrete factory and client code, many factories can be created and used interchangeably, for instance, like choosing between a light mode and dark mode theme.

Abstract factories are also useful for cross-platform applications that need to utilize different but similarly functioning libraries depending on the OS.

Conceptualization

In this example, we'll be creating an abstract factory that allows us to have a dark mode and a light mode in our application. By the end, our design will look similar to this model:

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/AbstractFactory_02.png" alt="abstract factory with UI" style="max-width:95%;"> </div>

Getting the Basics

To start with the basics, let's think about a good approach to build the UI with only a single theme in mind; if we designed it so that we can call our functions from one class that returns pre-assembled UI, then we could easily call on this throughout the application where needed:

 class UIObjectFactory {
   UIGraph createGraph() {/*..*/}
   UIButton createButton() {/*..*/}
 }

What was just described is plain and simply a factory pattern! Now, what if we decide that we want to add the ability for a dark mode to be toggled by the user; how should the design change to accommodate this elegantly?

Abstracting the Factories

By extracting the methods we defined in the original factory to an abstract factory class, we can create another factory that implements the UI with a dark theme appearance.

 // Our abstracted factory:
abstract class AbstractThemeFactory() {
   abstract UIGraph createGraph();
   abstract UIButton createButton();
 }

 // The Factories extend the abstract factory.
 class DarkThemeFactory extends AbstractThemeFactory {
   UIGraph createGraph() {}
   UIButton createButton() {}
 }
// Refactored from "UIObjectFactory"
 class LightThemeFactory extends AbstractThemeFactory {
   UIGraph createGraph() {}
   UIButton createButton() {}
 }

Abstracting The UI

For the sake of this example, to implement our UI and return an assembled UI object, we can create helper classes that each implement the UI in the two themes to hide the implementation from the factory.

As seen above, we've already labeled the two classes as UIGraph and UIButton.

// Abstract UI graph and button. 
 abstract class UIGraph {
   void draw();
 }
 abstract class UIButton {
   void draw();
 }
 // Concrete classes that implement the two in their modes.
 //---Dark Mode---//
 class DarkModeGraph extends UIGraph() {/*Implementation*/}
 class DarkModeButton extends UIButton() {/*Implementation*/}
 //---Light Mode---//
 class LightModeGraph extends UIGraph() {/*Implementation*/}
 class LightModeButton extends UIButton() {/*Implementation*/}

Putting it Together

Now that we have our abstract factory in place, we are able to use it to create instances of the two themes without the client code needing to know the specifics of either concrete factory. When the code is brought together, the final solution looks something like this:

 /*---pseudocode---*/
 // The abstract factory definition for our UI.
 abstract class AbstractThemeFactory {
   // Two basic methods that we might use in both factories:
   abstract UIGraph createGraph();
   abstract UIButton createButton();
 }

 // Concrete dark mode factory.
 class DarkThemeFactory extends AbstractThemeFactory {
   // Overridden methods of the abstract theme factory:
   UIGraph createGraph() {
     return new DarkModeGraph();
   }
   UIButton createButton() {
     return new DarkModeButton();
   }
 }
 // Concrete light mode factory.
 class LightThemeFactory extends AbstractThemeFactory {
   UIGraph createGraph() {
     return new LightModeGraph();
   }
   UIButton createButton() {
     return new LightModeButton();
   }
 }

 //---Some helper classes---//
 // Abstract UI graph and button. 
 abstract class UIGraph {
   void draw();
 }
 abstract class UIButton {
   void draw();
 }
 // Concrete classes that implement the two in their modes.
 //---Dark Mode---//
 class DarkModeGraph extends UIGraph() {/*Implementation*/}
 class DarkModeButton extends UIButton() {/*Implementation*/}
 //---Light Mode---//
 class LightModeGraph extends UIGraph() {/*Implementation*/}
 class LightModeButton extends UIButton() {/*Implementation*/}

 // Client code that would implement the abstract factory
 class ExampleSolution {

   static void main(String args) {
     // Creates a placeholder for the factory.
     AbstractThemeFactory factory;
     // Some arbitrary display mode of the current application.
     var mode = Application.DisplayMode;
     // Comparing the modes:
     switch (mode) {
       case DisplayMode.Dark:
         factory = new DarkThemeFactory();
         break;
       case DisplayMode.Light:
         factory = new LightThemeFactory();
         break;
       // If it's something we haven't covered, we can choose what to do.
       default:
         throw new Exception();
     }

     // With our factory, create a button and graph:
     UIGraph graph = factory.createGraph();
     UIButton button = factory.createButton();
     // Draw the constructed UI items to the screen:
     graph.draw();
     button.draw();
     // Done!
   }
 }

If you wanted to add a new display mode, it would only require creating a new factory!