mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
abstract factory example
This commit is contained in:
parent
472d144b42
commit
2c7ae24791
9 changed files with 195 additions and 0 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
# Creational Patterns
|
# Creational Patterns
|
||||||
|
|
||||||
- [Factory Method](factory-method/README.md)
|
- [Factory Method](factory-method/README.md)
|
||||||
|
- [Abstract Factory](abstract-factory/README.md)
|
||||||
|
|
|
||||||
22
creational/abstract-factory/AbstractFactory.py
Normal file
22
creational/abstract-factory/AbstractFactory.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from AbstractProductA import AbstractProductA
|
||||||
|
from AbstractProductB import AbstractProductB
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractFactory(ABC):
|
||||||
|
"""
|
||||||
|
The Abstract Factory interface declares a set of methods that return
|
||||||
|
different abstract products. These products are called a family and are
|
||||||
|
related by a high-level theme or concept. Products of one family are usually
|
||||||
|
able to collaborate among themselves. A family of products may have several
|
||||||
|
variants, but the products of one variant are incompatible with products of
|
||||||
|
another.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def create_product_a(self) -> AbstractProductA:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def create_product_b(self) -> AbstractProductB:
|
||||||
|
pass
|
||||||
11
creational/abstract-factory/AbstractProductA.py
Normal file
11
creational/abstract-factory/AbstractProductA.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractProductA(ABC):
|
||||||
|
"""
|
||||||
|
Each distinct product of a product family should have a base interface. All
|
||||||
|
variants of the product must implement this interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def useful_function_a(self) -> str:
|
||||||
|
pass
|
||||||
27
creational/abstract-factory/AbstractProductB.py
Normal file
27
creational/abstract-factory/AbstractProductB.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from AbstractProductA import AbstractProductA
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractProductB(ABC):
|
||||||
|
"""
|
||||||
|
Here's the the base interface of another product. All products can interact
|
||||||
|
with each other, but proper interaction is possible only between products of
|
||||||
|
the same concrete variant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def useful_function_b(self) -> None:
|
||||||
|
"""
|
||||||
|
Product B is able to do its own thing...
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def another_useful_function_b(self, collaborator: AbstractProductA) -> None:
|
||||||
|
"""
|
||||||
|
...but it also can collaborate with the ProductA.
|
||||||
|
|
||||||
|
The Abstract Factory makes sure that all products it creates are of the
|
||||||
|
same variant and thus, compatible.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
35
creational/abstract-factory/README.md
Normal file
35
creational/abstract-factory/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Abstract Factory
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Imagine that you’re creating a furniture shop simulator. Your code consists of classes that represent:
|
||||||
|
|
||||||
|
A family of related products, say: `Chair` + `Sofa` + `CoffeeTable`.
|
||||||
|
|
||||||
|
Several variants of this family. For example, products `Chair` + `Sofa` + `CoffeeTable` are available in these variants: `Modern`, `Victorian`, `ArtDeco`.
|
||||||
|
|
||||||
|
You need a way to create individual furniture objects so that they match other objects of the same family. Customers get quite mad when they receive non-matching furniture.
|
||||||
|
|
||||||
|
Also, you don’t want to change existing code when adding new products or families of products to the program. Furniture vendors update their catalogs very often, and you wouldn’t want to change the core code each time it happens.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
The first thing the Abstract Factory pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., chair, sofa or coffee table). Then you can make all variants of products follow those interfaces. For example, all chair variants can implement the `Chair` interface; all coffee table variants can implement the `CoffeeTable` interface, and so on.
|
||||||
|
|
||||||
|
## How to Implement
|
||||||
|
|
||||||
|
1. Map out a matrix of distinct product types versus variants of these products.
|
||||||
|
|
||||||
|
1. Declare abstract product interfaces for all product types. Then make all concrete product classes implement these interfaces.
|
||||||
|
|
||||||
|
1. Declare the abstract factory interface with a set of creation methods for all abstract products.
|
||||||
|
|
||||||
|
1. Implement a set of concrete factory classes, one for each product variant.
|
||||||
|
|
||||||
|
1. Create factory initialization code somewhere in the app. It should instantiate one of the concrete factory classes, depending on the application configuration or the current environment. Pass this factory object to all classes that construct products.
|
||||||
|
|
||||||
|
1. Scan through the code and find all direct calls to product constructors. Replace them with calls to the appropriate creation method on the factory object.
|
||||||
0
creational/abstract-factory/__init__.py
Normal file
0
creational/abstract-factory/__init__.py
Normal file
30
creational/abstract-factory/factories.py
Normal file
30
creational/abstract-factory/factories.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from AbstractFactory import AbstractFactory
|
||||||
|
from products import (ConcreteProductA1, ConcreteProductA2,
|
||||||
|
ConcreteProductB1, ConcreteProductB2)
|
||||||
|
|
||||||
|
|
||||||
|
class ConcreteFactory1(AbstractFactory):
|
||||||
|
"""
|
||||||
|
Concrete Factories produce a family of products that belong to a single
|
||||||
|
variant. The factory guarantees that resulting products are compatible. Note
|
||||||
|
that signatures of the Concrete Factory's methods return an abstract
|
||||||
|
product, while inside the method a concrete product is instantiated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_product_a(self) -> ConcreteProductA1:
|
||||||
|
return ConcreteProductA1()
|
||||||
|
|
||||||
|
def create_product_b(self) -> ConcreteProductB1:
|
||||||
|
return ConcreteProductB1()
|
||||||
|
|
||||||
|
|
||||||
|
class ConcreteFactory2(AbstractFactory):
|
||||||
|
"""
|
||||||
|
Each Concrete Factory has a corresponding product variant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_product_a(self) -> ConcreteProductA2:
|
||||||
|
return ConcreteProductA2()
|
||||||
|
|
||||||
|
def create_product_b(self) -> ConcreteProductB2:
|
||||||
|
return ConcreteProductB2()
|
||||||
26
creational/abstract-factory/main.py
Normal file
26
creational/abstract-factory/main.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
"""The client code can work with any concrete factory class."""
|
||||||
|
|
||||||
|
from AbstractFactory import AbstractFactory
|
||||||
|
from factories import ConcreteFactory1, ConcreteFactory2
|
||||||
|
|
||||||
|
|
||||||
|
def client_code(factory: AbstractFactory) -> None:
|
||||||
|
"""
|
||||||
|
The client code works with factories and products only through abstract
|
||||||
|
types: AbstractFactory and AbstractProduct. This lets you pass any factory
|
||||||
|
or product subclass to the client code without breaking it.
|
||||||
|
"""
|
||||||
|
product_a = factory.create_product_a()
|
||||||
|
product_b = factory.create_product_b()
|
||||||
|
|
||||||
|
print(f"{product_b.useful_function_b()}")
|
||||||
|
print(f"{product_b.another_useful_function_b(product_a)}", end="")
|
||||||
|
|
||||||
|
|
||||||
|
print("Client: Testing client code with the first factory type:")
|
||||||
|
client_code(ConcreteFactory1())
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
print("Client: Testing the same client code with the second factory type:")
|
||||||
|
client_code(ConcreteFactory2())
|
||||||
43
creational/abstract-factory/products.py
Normal file
43
creational/abstract-factory/products.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""Concrete Products are created by corresponding Concrete Factories."""
|
||||||
|
|
||||||
|
from AbstractProductA import AbstractProductA
|
||||||
|
from AbstractProductB import AbstractProductB
|
||||||
|
|
||||||
|
|
||||||
|
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 ConcreteProductB1(AbstractProductB):
|
||||||
|
|
||||||
|
def useful_function_b(self) -> str:
|
||||||
|
return "The result of the product B1."
|
||||||
|
|
||||||
|
def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
|
||||||
|
"""
|
||||||
|
The variant, Product B1, is only able to work correctly with the variant,
|
||||||
|
Product A1. Nevertheless, it accepts any instance of AbstractProductA as an
|
||||||
|
argument.
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
The variant, Product B2, is only able to work correctly with the
|
||||||
|
variant, Product A2. Nevertheless, it accepts any instance of
|
||||||
|
AbstractProductA as an argument.
|
||||||
|
"""
|
||||||
|
result = collaborator.useful_function_a()
|
||||||
|
return f"The result of the B2 collaborating with the ({result})"
|
||||||
Loading…
Reference in a new issue