builder example

This commit is contained in:
Ruidy Nemausat 2020-09-12 12:56:26 +02:00
parent 220d02a13d
commit 490c2646f9
13 changed files with 202 additions and 2 deletions

View file

@ -6,3 +6,5 @@
- [Creational Patterns](creational/README.md)
- [Factory Method](creational/factory-method/README.md)
- [Abstract Factory](creational/abstract-factory/README.md)
- [Builder](creational/builder/README.md)

View file

@ -2,3 +2,4 @@
- [Factory Method](factory-method/README.md)
- [Abstract Factory](abstract-factory/README.md)
- [Builder](builder/README.md)

View file

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from AbstractProductA import AbstractProductA
from AbstractProductB import AbstractProductB

View file

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from AbstractProductA import AbstractProductA

View file

@ -1,6 +1,6 @@
from AbstractFactory import AbstractFactory
from products import (ConcreteProductA1, ConcreteProductA2,
ConcreteProductB1, ConcreteProductB2)
from products import (ConcreteProductA1, ConcreteProductA2, ConcreteProductB1,
ConcreteProductB2)
class ConcreteFactory1(AbstractFactory):

View 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

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

View 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, lets 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 wont 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.
Dont forget about implementing a method for fetching the result of the construction. The reason why this method cant be declared inside the builder interface is that various builders may construct products that dont have a common interface. Therefore, you dont know what would be the return type for such a method. However, if youre 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 directors constructor. The director uses the builder object in all further construction. Theres 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.

View file

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

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

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

View file

@ -4,6 +4,7 @@ product's type.
"""
from __future__ import annotations
from ICreator import ICreator
from products import ConcreteProduct1, ConcreteProduct2