001
creational GOF

Abstract Factory

Reference Wikipedia ↗
Abstract Factory — class diagram
Plate 001 class diagram

The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It allows a system to be independent of how its products are created, composed, and represented. Effectively, it’s a “factory of factories”—a way to delegate the responsibility of object creation to other objects.

This pattern is particularly useful when you need to create different combinations of related objects that depend on a configuration or platform. It promotes loose coupling between classes and makes it easy to switch between different “looks and feels” or object implementations without modifying the client code. It addresses the issue of creating multiple coupled object families when a simple factory isn’t flexible enough.

Usage

The Abstract Factory pattern is commonly used in these scenarios:

  • GUI Frameworks: Creating widgets (buttons, text fields, etc.) that are specific to a particular operating system (Windows, macOS, Linux). Each OS needs a distinct set of widgets.
  • Database Abstraction: Providing an abstraction layer for different database systems (MySQL, PostgreSQL, Oracle). An abstract factory can create database connections, queries, and commands.
  • Configuration Management: Dynamically loading and configuring different sets of components based on a configuration file or environment variable.
  • Cross-Platform Development: Where the same high-level code needs to interact with platform-specific implementations.

Examples

  1. Java Swing/JFace: Java’s Swing and Eclipse’s JFace frameworks utilize abstract factories extensively. They provide different “look and feel” factories that allow applications to easily adapt to different operating systems and user preferences. Each factory creates a complete set of UI components—buttons, text fields, scrollbars, etc.—that share a consistent style.

  2. Spring Framework (Bean Definition Factories): Spring’s configuration mechanism uses an abstract factory approach. While not directly named as such, the BeanFactory (and its implementations like XmlBeanFactory or AnnotationConfigBeanFactory) effectively act as abstract factories for creating and managing beans within the application context. Different BeanFactory implementations use differing sources for bean definitions (XML, annotations, Java config) but provide a consistent interface for retrieving beans.

  3. Unity Game Engine: Unity’s Asset Serialization system can leverage Abstract Factories. Different asset formats (e.g., FBX, OBJ, custom formats) can have different serialization/deserialization methods. An abstract factory could be used to provide a common interface for creating asset importers and exporters tailored to specific asset types without the core engine needing to know the details of each format.

Specimens

15 implementations
Specimen 001.01 Dart View specimen ↗

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It’s a creational pattern that promotes loose coupling and allows for easy switching between different “themes” or variations of objects.

This Dart implementation defines an AbstractFactory interface with a method to create a ProductA and a ProductB. Concrete factories, like ConcreteFactory1 and ConcreteFactory2, implement this interface to produce specific variations of these products. A Client class then uses the factory interface to obtain objects without knowing their concrete types. This adheres to Dart’s preference for interfaces and abstract classes for defining contracts, and utilizes constructor injection for dependency management, making the code testable and maintainable.

abstract class ProductA {
  void interact(ProductB b);
}

abstract class ProductB {
  void interact(ProductA a);
}

abstract class AbstractFactory {
  ProductA createProductA();
  ProductB createProductB();
}

class ConcreteProductA1 implements ProductA {
  @override
  void interact(ProductB b) {
    print('ConcreteProductA1 interacts with ProductB');
    b.interact(this);
  }
}

class ConcreteProductB1 implements ProductB {
  @override
  void interact(ProductA a) {
    print('ConcreteProductB1 interacts with ProductA');
  }
}

class ConcreteProductA2 implements ProductA {
  @override
  void interact(ProductB b) {
    print('ConcreteProductA2 interacts with ProductB');
    b.interact(this);
  }
}

class ConcreteProductB2 implements ProductB {
  @override
  void interact(ProductA a) {
    print('ConcreteProductB2 interacts with ProductA');
  }
}

class ConcreteFactory1 implements AbstractFactory {
  @override
  ProductA createProductA() {
    return ConcreteProductA1();
  }

  @override
  ProductB createProductB() {
    return ConcreteProductB1();
  }
}

class ConcreteFactory2 implements AbstractFactory {
  @override
  ProductA createProductA() {
    return ConcreteProductA2();
  }

  @override
  ProductB createProductB() {
    return ConcreteProductB2();
  }
}

class Client {
  final AbstractFactory factory;

  Client(this.factory);

  void run() {
    final productA = factory.createProductA();
    final productB = factory.createProductB();

    productA.interact(productB);
  }
}

void main() {
  final factory1 = ConcreteFactory1();
  final client1 = Client(factory1);
  client1.run();

  final factory2 = ConcreteFactory2();
  final client2 = Client(factory2);
  client2.run();
}