abstract factory example

This commit is contained in:
Ruidy Nemausat 2020-09-12 00:45:31 +02:00
parent 472d144b42
commit 2c7ae24791
9 changed files with 195 additions and 0 deletions

View file

@ -1,3 +1,4 @@
# Creational Patterns
- [Factory Method](factory-method/README.md)
- [Abstract Factory](abstract-factory/README.md)

View 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

View 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

View 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

View 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 youre 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 dont 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 wouldnt 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.

View file

View 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()

View 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())

View 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})"