Definition

“Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes”
abstract-factory

Explanation

You define an abstract factory interface (e.g., GUIFactory) with methods to create various related products (e.g. createButton(), createCheckbox()). Concrete factories (WindowsFactory, MacFactory) implement these methods to produce OS-specific components. Client code uses the abstract factory interface to create components, so it stays agnostic of concrete classes. This ensures products from the same family are used together.

Code

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. In this code, AbstractFactory declares methods for creating abstract products A and B. ConcreteFactory1 and ConcreteFactory2 produce corresponding concrete products (ConcreteProductA1/A2, ConcreteProductB1/B2). The client_code function works with factories to produce products and invoke their methods.
from abc import ABC, abstractmethod

class AbstractFactory(ABC):
    @abstractmethod
    def create_product_a(self) -> "AbstractProductA":
        pass
    @abstractmethod
    def create_product_b(self) -> "AbstractProductB":
        pass

class ConcreteFactory1(AbstractFactory):
    def create_product_a(self) -> "AbstractProductA":
        return ConcreteProductA1()
    def create_product_b(self) -> "AbstractProductB":
        return ConcreteProductB1()
class ConcreteFactory2(AbstractFactory):
    def create_product_a(self) -> "AbstractProductA":
        return ConcreteProductA2()
    def create_product_b(self) -> "AbstractProductB":
        return ConcreteProductB2()

class AbstractProductA(ABC):
    @abstractmethod
    def useful_function_a(self) -> str:
        pass

class ConcreteProductA1(AbstractProductA):
    def useful_function_a(self) -> str:
        return "The result of the product A1."
class ConcreteProductA2(AbstractProductA):
    def useful_function_a(self) -> str:
        return "The result of the product A2."

class AbstractProductB(ABC):
    @abstractmethod
    def useful_function_b(self) -> str:
        pass
    @abstractmethod
    def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
        pass

class ConcreteProductB1(AbstractProductB):
    def useful_function_b(self) -> str:
        return "The result of the product B1."
    def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
        result = collaborator.useful_function_a()
        return f"The result of the B1 collaborating with the ({result})"
class ConcreteProductB2(AbstractProductB):
    def useful_function_b(self) -> str:
        return "The result of the product B2."
    def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
        result = collaborator.useful_function_a()
        return f"The result of the B2 collaborating with the ({result})"

def client_code(factory: AbstractFactory) -> None:
    product_a = factory.create_product_a()
    product_b = factory.create_product_b()
    print(product_b.useful_function_b())
    print(product_b.another_useful_function_b(product_a), end="")

def main():
    print("Client: Testing client code with the first factory type:")
    client_code(ConcreteFactory1())
    print("\nClient: Testing with the second factory type:")
    client_code(ConcreteFactory2())
    print()

if __name__ == "__main__":
    main()

Analogy

Think of a furniture showroom: you pick a modern or Victorian style, which is an abstract factory. All furniture you get (chairs, tables, sofas) will match that style. If you choose “Victorian”, the Victorian factory gives you Victorian chairs/tables. Client code just asks for createChair(), createTable() on the selected factory and gets matching items, with no knowledge of concrete styles.

Interview Insights

Common uses: When a system needs to be independent of how its products are created. Useful for cross-platform UI widgets (ensuring all controls belong to same theme), or when swapping entire product families (e.g. dark vs light theme packs). Often used with Factory Methods to get factories.
Advantages: Enforces consistency among related products. Decouples concrete product classes from client. Easy to switch entire families of products by plugging a different factory.
Disadvantages: Hard to add new product types (methods on factory interface must be added, all factories updated). Many classes (one factory per family). Can be overkill if you only need one product type.\