From 490c2646f9f8a6c6e466745920cceecf26df7dea Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Sat, 12 Sep 2020 12:56:26 +0200 Subject: [PATCH] builder example --- README.md | 2 + creational/README.md | 1 + .../abstract-factory/AbstractFactory.py | 1 + .../abstract-factory/AbstractProductB.py | 1 + creational/abstract-factory/factories.py | 4 +- creational/builder/Builder.py | 24 +++++++++ creational/builder/Director.py | 37 ++++++++++++++ creational/builder/README.md | 32 ++++++++++++ creational/builder/__init__.py | 0 creational/builder/builders.py | 50 +++++++++++++++++++ creational/builder/main.py | 30 +++++++++++ creational/builder/products.py | 21 ++++++++ creational/factory-method/creators.py | 1 + 13 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 creational/builder/Builder.py create mode 100644 creational/builder/Director.py create mode 100644 creational/builder/README.md create mode 100644 creational/builder/__init__.py create mode 100644 creational/builder/builders.py create mode 100644 creational/builder/main.py create mode 100644 creational/builder/products.py diff --git a/README.md b/README.md index 734e68c..a37d1f4 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/creational/README.md b/creational/README.md index 1cfe3a3..d719ad8 100644 --- a/creational/README.md +++ b/creational/README.md @@ -2,3 +2,4 @@ - [Factory Method](factory-method/README.md) - [Abstract Factory](abstract-factory/README.md) +- [Builder](builder/README.md) diff --git a/creational/abstract-factory/AbstractFactory.py b/creational/abstract-factory/AbstractFactory.py index bd3dca8..2f86dd5 100644 --- a/creational/abstract-factory/AbstractFactory.py +++ b/creational/abstract-factory/AbstractFactory.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod + from AbstractProductA import AbstractProductA from AbstractProductB import AbstractProductB diff --git a/creational/abstract-factory/AbstractProductB.py b/creational/abstract-factory/AbstractProductB.py index b6fc1b5..7264b01 100644 --- a/creational/abstract-factory/AbstractProductB.py +++ b/creational/abstract-factory/AbstractProductB.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod + from AbstractProductA import AbstractProductA diff --git a/creational/abstract-factory/factories.py b/creational/abstract-factory/factories.py index 1f7c89b..8716f54 100644 --- a/creational/abstract-factory/factories.py +++ b/creational/abstract-factory/factories.py @@ -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): diff --git a/creational/builder/Builder.py b/creational/builder/Builder.py new file mode 100644 index 0000000..515ef7c --- /dev/null +++ b/creational/builder/Builder.py @@ -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 diff --git a/creational/builder/Director.py b/creational/builder/Director.py new file mode 100644 index 0000000..2ba7c3a --- /dev/null +++ b/creational/builder/Director.py @@ -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() diff --git a/creational/builder/README.md b/creational/builder/README.md new file mode 100644 index 0000000..538feac --- /dev/null +++ b/creational/builder/README.md @@ -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. diff --git a/creational/builder/__init__.py b/creational/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/creational/builder/builders.py b/creational/builder/builders.py new file mode 100644 index 0000000..2f92c62 --- /dev/null +++ b/creational/builder/builders.py @@ -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") diff --git a/creational/builder/main.py b/creational/builder/main.py new file mode 100644 index 0000000..c4e6c4f --- /dev/null +++ b/creational/builder/main.py @@ -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() diff --git a/creational/builder/products.py b/creational/builder/products.py new file mode 100644 index 0000000..22eb462 --- /dev/null +++ b/creational/builder/products.py @@ -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="") diff --git a/creational/factory-method/creators.py b/creational/factory-method/creators.py index 0c43eb8..6816022 100644 --- a/creational/factory-method/creators.py +++ b/creational/factory-method/creators.py @@ -4,6 +4,7 @@ product's type. """ from __future__ import annotations + from ICreator import ICreator from products import ConcreteProduct1, ConcreteProduct2