mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
builder example
This commit is contained in:
parent
220d02a13d
commit
490c2646f9
13 changed files with 202 additions and 2 deletions
|
|
@ -6,3 +6,5 @@
|
||||||
|
|
||||||
- [Creational Patterns](creational/README.md)
|
- [Creational Patterns](creational/README.md)
|
||||||
- [Factory Method](creational/factory-method/README.md)
|
- [Factory Method](creational/factory-method/README.md)
|
||||||
|
- [Abstract Factory](creational/abstract-factory/README.md)
|
||||||
|
- [Builder](creational/builder/README.md)
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
|
|
||||||
- [Factory Method](factory-method/README.md)
|
- [Factory Method](factory-method/README.md)
|
||||||
- [Abstract Factory](abstract-factory/README.md)
|
- [Abstract Factory](abstract-factory/README.md)
|
||||||
|
- [Builder](builder/README.md)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from AbstractProductA import AbstractProductA
|
from AbstractProductA import AbstractProductA
|
||||||
from AbstractProductB import AbstractProductB
|
from AbstractProductB import AbstractProductB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from AbstractProductA import AbstractProductA
|
from AbstractProductA import AbstractProductA
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from AbstractFactory import AbstractFactory
|
from AbstractFactory import AbstractFactory
|
||||||
from products import (ConcreteProductA1, ConcreteProductA2,
|
from products import (ConcreteProductA1, ConcreteProductA2, ConcreteProductB1,
|
||||||
ConcreteProductB1, ConcreteProductB2)
|
ConcreteProductB2)
|
||||||
|
|
||||||
|
|
||||||
class ConcreteFactory1(AbstractFactory):
|
class ConcreteFactory1(AbstractFactory):
|
||||||
|
|
|
||||||
24
creational/builder/Builder.py
Normal file
24
creational/builder/Builder.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from abc import ABC, abstractmethod, abstractproperty
|
||||||
|
|
||||||
|
|
||||||
|
class Builder(ABC):
|
||||||
|
"""
|
||||||
|
The Builder interface specifies methods for creating the different parts of
|
||||||
|
the Product objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractproperty
|
||||||
|
def product(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def produce_part_a(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def produce_part_b(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def produce_part_c(self) -> None:
|
||||||
|
pass
|
||||||
37
creational/builder/Director.py
Normal file
37
creational/builder/Director.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from Builder import Builder
|
||||||
|
|
||||||
|
|
||||||
|
class Director:
|
||||||
|
"""
|
||||||
|
The Director is only responsible for executing the building steps in a
|
||||||
|
particular sequence. It is helpful when producing products according to a
|
||||||
|
specific order or configuration. Strictly speaking, the Director class is
|
||||||
|
optional, since the client can control builders directly.
|
||||||
|
|
||||||
|
The Director can construct several product variations using the same
|
||||||
|
building steps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._builder = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def builder(self) -> Builder:
|
||||||
|
return self._builder
|
||||||
|
|
||||||
|
@builder.setter
|
||||||
|
def builder(self, builder: Builder) -> None:
|
||||||
|
"""
|
||||||
|
The Director works with any builder instance that the client code passes
|
||||||
|
to it. This way, the client code may alter the final type of the newly
|
||||||
|
assembled product.
|
||||||
|
"""
|
||||||
|
self._builder = builder
|
||||||
|
|
||||||
|
def build_minimal_viable_product(self) -> None:
|
||||||
|
self._builder.produce_part_a()
|
||||||
|
|
||||||
|
def build_full_featured_product(self) -> None:
|
||||||
|
self._builder.produce_part_a()
|
||||||
|
self._builder.produce_part_b()
|
||||||
|
self._builder.produce_part_c()
|
||||||
32
creational/builder/README.md
Normal file
32
creational/builder/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Builder
|
||||||
|
|
||||||
|
Builder is a creational design pattern, which allows constructing complex objects step by step.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters. Or even worse: scattered all over the client code.
|
||||||
|
|
||||||
|
For example, let’s think about how to create a `House` object. To build a simple house, you need to construct four walls and a floor, install a door, fit a pair of windows, and build a roof. But what if you want a bigger, brighter house, with a backyard and other goodies (like a heating system, plumbing, and electrical wiring)?
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
The Builder pattern suggests that you extract the object construction code out of its own class and move it to separate objects called builders.
|
||||||
|
|
||||||
|
## How to Implement
|
||||||
|
|
||||||
|
1. Make sure that you can clearly define the common construction steps for building all available product representations. Otherwise, you won’t be able to proceed with implementing the pattern.
|
||||||
|
|
||||||
|
1. Declare these steps in the base builder interface.
|
||||||
|
|
||||||
|
1. Create a concrete builder class for each of the product representations and implement their construction steps.
|
||||||
|
Don’t forget about implementing a method for fetching the result of the construction. The reason why this method can’t be declared inside the builder interface is that various builders may construct products that don’t have a common interface. Therefore, you don’t know what would be the return type for such a method. However, if you’re dealing with products from a single hierarchy, the fetching method can be safely added to the base interface.
|
||||||
|
|
||||||
|
1. Think about creating a director class. It may encapsulate various ways to construct a product using the same builder object.
|
||||||
|
|
||||||
|
1. The client code creates both the builder and the director objects. Before construction starts, the client must pass a builder object to the director. Usually, the client does this only once, via parameters of the director’s constructor. The director uses the builder object in all further construction. There’s an alternative approach, where the builder is passed directly to the construction method of the director.
|
||||||
|
|
||||||
|
1. The construction result can be obtained directly from the director only if all products follow the same interface. Otherwise, the client should fetch the result from the builder.
|
||||||
0
creational/builder/__init__.py
Normal file
0
creational/builder/__init__.py
Normal file
50
creational/builder/builders.py
Normal file
50
creational/builder/builders.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from Builder import Builder
|
||||||
|
from products import Product1
|
||||||
|
|
||||||
|
|
||||||
|
class ConcreteBuilder1(Builder):
|
||||||
|
"""
|
||||||
|
The Concrete Builder classes follow the Builder interface and provide
|
||||||
|
specific implementations of the building steps. Your program may have
|
||||||
|
several variations of Builders, implemented differently.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
A fresh builder instance should contain a blank product object, which is
|
||||||
|
used in further assembly.
|
||||||
|
"""
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self._product = Product1()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def product(self) -> Product1:
|
||||||
|
"""
|
||||||
|
Concrete Builders are supposed to provide their own methods for
|
||||||
|
retrieving results. That's because various types of builders may create
|
||||||
|
entirely different products that don't follow the same interface.
|
||||||
|
Therefore, such methods cannot be declared in the base Builder interface
|
||||||
|
(at least in a statically typed programming language).
|
||||||
|
|
||||||
|
Usually, after returning the end result to the client, a builder
|
||||||
|
instance is expected to be ready to start producing another product.
|
||||||
|
That's why it's a usual practice to call the reset method at the end of
|
||||||
|
the `getProduct` method body. However, this behavior is not mandatory,
|
||||||
|
and you can make your builders wait for an explicit reset call from the
|
||||||
|
client code before disposing of the previous result.
|
||||||
|
"""
|
||||||
|
product = self._product
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
return product
|
||||||
|
|
||||||
|
def produce_part_a(self) -> None:
|
||||||
|
self._product.add("PartA1")
|
||||||
|
|
||||||
|
def produce_part_b(self) -> None:
|
||||||
|
self._product.add("PartB1")
|
||||||
|
|
||||||
|
def produce_part_c(self) -> None:
|
||||||
|
self._product.add("PartC1")
|
||||||
30
creational/builder/main.py
Normal file
30
creational/builder/main.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""
|
||||||
|
The client code creates a builder object, passes it to the director and then
|
||||||
|
initiates the construction process. The end result is retrieved from the
|
||||||
|
builder object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from builders import ConcreteBuilder1
|
||||||
|
from Director import Director
|
||||||
|
|
||||||
|
director = Director()
|
||||||
|
builder = ConcreteBuilder1()
|
||||||
|
director.builder = builder
|
||||||
|
|
||||||
|
print("Standard basic product: ")
|
||||||
|
director.build_minimal_viable_product()
|
||||||
|
builder.product.list_parts()
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
print("Standard full featured product: ")
|
||||||
|
director.build_full_featured_product()
|
||||||
|
builder.product.list_parts()
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
# Remember, the Builder pattern can be used without a Director class.
|
||||||
|
print("Custom product: ")
|
||||||
|
builder.produce_part_a()
|
||||||
|
builder.produce_part_b()
|
||||||
|
builder.product.list_parts()
|
||||||
21
creational/builder/products.py
Normal file
21
creational/builder/products.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
class Product1:
|
||||||
|
"""
|
||||||
|
It makes sense to use the Builder pattern only when your products are quite
|
||||||
|
complex and require extensive configuration.
|
||||||
|
|
||||||
|
Unlike in other creational patterns, different concrete builders can produce
|
||||||
|
unrelated products. In other words, results of various builders may not
|
||||||
|
always follow the same interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
def add(self, part: Any) -> None:
|
||||||
|
self.parts.append(part)
|
||||||
|
|
||||||
|
def list_parts(self) -> None:
|
||||||
|
print(f"Product parts: {', '.join(self.parts)}", end="")
|
||||||
|
|
@ -4,6 +4,7 @@ product's type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ICreator import ICreator
|
from ICreator import ICreator
|
||||||
from products import ConcreteProduct1, ConcreteProduct2
|
from products import ConcreteProduct1, ConcreteProduct2
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue