Skip to content

Commit 8444c56

Browse files
committed
add builder pattern to python patterns plus associated tests
1 parent 3dadd82 commit 8444c56

File tree

7 files changed

+214
-0
lines changed

7 files changed

+214
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .builder import Builder, ConcreteBuilder
2+
from .product import Product
3+
from .director import Director
4+
5+
__all__ = ["Builder", "ConcreteBuilder", "Product", "Director"]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from abc import ABC, abstractmethod
2+
from .product import Product
3+
4+
5+
class Builder(ABC):
6+
"""
7+
Abstract Builder: Declares product creation steps.
8+
"""
9+
10+
@abstractmethod
11+
def reset(self):
12+
pass
13+
14+
@abstractmethod
15+
def build_part_a(self):
16+
pass
17+
18+
@abstractmethod
19+
def build_part_b(self):
20+
pass
21+
22+
@abstractmethod
23+
def get_result(self) -> Product:
24+
pass
25+
26+
27+
class ConcreteBuilder(Builder):
28+
"""
29+
Concrete Builder: Implements the building steps.
30+
"""
31+
32+
def __init__(self):
33+
self.product = Product()
34+
35+
def reset(self):
36+
self.product = Product()
37+
38+
def build_part_a(self):
39+
self.product.add_part("Part A")
40+
41+
def build_part_b(self):
42+
self.product.add_part("Part B")
43+
44+
def get_result(self) -> Product:
45+
return self.product
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from .builder import Builder
2+
3+
4+
class Director:
5+
"""
6+
Director: Defines the order in which steps are executed.
7+
"""
8+
9+
def __init__(self, builder: Builder):
10+
self.builder = builder
11+
12+
def construct_minimal_product(self):
13+
"""
14+
Constructs a minimal product with only part A.
15+
"""
16+
self.builder.reset()
17+
self.builder.build_part_a()
18+
19+
def construct_full_product(self):
20+
"""
21+
Constructs a full product with both part A and part B.
22+
"""
23+
self.builder.reset()
24+
self.builder.build_part_a()
25+
self.builder.build_part_b()
26+
27+
# Example Usage:
28+
#
29+
# from src.creational.builder.builder import ConcreteBuilder
30+
# from src.creational.builder.director import Director
31+
#
32+
# builder = ConcreteBuilder()
33+
# director = Director(builder)
34+
#
35+
# director.construct_full_product()
36+
# product = builder.get_result()
37+
#
38+
# print(product.list_parts()) # Output: "Part A, Part B"
39+
#
40+
# Common Mistakes to Avoid:
41+
#
42+
# 1. Forgetting to Call `reset()`:
43+
# - Mistake: Not resetting the builder before constructing a new product.
44+
# - Fix: Always call `reset()` before starting a new build process.
45+
#
46+
# 2. Using Builder When Simple Instantiation Works:
47+
# - Mistake: Using Builder Pattern when the product can be created easily
48+
# via a constructor.
49+
# - Fix: Only use Builder when constructing the product is complex.
50+
#
51+
# 3. Tightly Coupling Director to Concrete Builders:
52+
# - Mistake: The Director should use the abstract `Builder` interface.
53+
# - Fix: Use dependency injection to allow different builders.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Product:
2+
"""
3+
Product: The complex object being built.
4+
"""
5+
6+
def __init__(self):
7+
self.parts = []
8+
9+
def add_part(self, part: str):
10+
"""
11+
Adds a new part to the product.
12+
"""
13+
self.parts.append(part)
14+
15+
def list_parts(self) -> str:
16+
"""
17+
Lists all parts of the product.
18+
"""
19+
return ", ".join(self.parts)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from src.creational.builder.builder import ConcreteBuilder
2+
from src.creational.builder.product import Product
3+
4+
5+
def test_concrete_builder_initialization():
6+
"""
7+
Test that a ConcreteBuilder is initialized with an empty product.
8+
"""
9+
builder = ConcreteBuilder()
10+
assert isinstance(builder.get_result(), Product)
11+
assert builder.get_result().list_parts() == ""
12+
13+
14+
def test_concrete_builder_build_part_a():
15+
"""
16+
Test that ConcreteBuilder correctly adds Part A.
17+
"""
18+
builder = ConcreteBuilder()
19+
builder.build_part_a()
20+
product = builder.get_result()
21+
assert product.list_parts() == "Part A"
22+
23+
24+
def test_concrete_builder_build_part_b():
25+
"""
26+
Test that ConcreteBuilder correctly adds Part B.
27+
"""
28+
builder = ConcreteBuilder()
29+
builder.build_part_b()
30+
product = builder.get_result()
31+
assert product.list_parts() == "Part B"
32+
33+
34+
def test_concrete_builder_reset():
35+
"""
36+
Test that calling reset() clears the product.
37+
"""
38+
builder = ConcreteBuilder()
39+
builder.build_part_a()
40+
builder.reset()
41+
assert builder.get_result().list_parts() == ""
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from src.creational.builder.director import Director
2+
from src.creational.builder.builder import ConcreteBuilder
3+
4+
5+
def test_director_construct_minimal_product():
6+
"""
7+
Test that the director correctly constructs a minimal product
8+
with only Part A.
9+
"""
10+
builder = ConcreteBuilder()
11+
director = Director(builder)
12+
13+
director.construct_minimal_product()
14+
product = builder.get_result()
15+
16+
assert product.list_parts() == "Part A"
17+
18+
19+
def test_director_construct_full_product():
20+
"""
21+
Test that the director correctly constructs a full product
22+
with Part A and Part B.
23+
"""
24+
builder = ConcreteBuilder()
25+
director = Director(builder)
26+
27+
director.construct_full_product()
28+
product = builder.get_result()
29+
30+
assert product.list_parts() == "Part A, Part B"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from src.creational.builder.product import Product
2+
3+
4+
def test_product_initialization():
5+
"""
6+
Test that a new product is initialized with an empty list.
7+
"""
8+
product = Product()
9+
assert product.list_parts() == ""
10+
11+
12+
def test_product_add_part():
13+
"""
14+
Test that parts can be added to the product.
15+
"""
16+
product = Product()
17+
product.add_part("Part A")
18+
assert product.list_parts() == "Part A"
19+
20+
product.add_part("Part B")
21+
assert product.list_parts() == "Part A, Part B"

0 commit comments

Comments
 (0)