Skip to content

Commit 6ceeaec

Browse files
committed
Edited creational patterns.
1 parent b3d77b2 commit 6ceeaec

File tree

7 files changed

+277
-130
lines changed

7 files changed

+277
-130
lines changed
Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,55 @@
11
# The Abstract Factory Pattern (Creational)
22

3-
## Intent
3+
## Purpose
44

5-
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's used to abstract away the details of how a set of products are created, composed and represented.
5+
The Abstract Factory pattern provides an interface for creating groups of related or dependent objects without needing to specify their exact classes. Its used to make systems more flexible and easier to extend by abstracting the creation of product families.
66

7-
## Problem It Solves
7+
## The Problem It Solves
88

9-
In software development, we often have systems that need to be flexible and adaptable. This is where the Abstract Factory pattern comes in handy. Imagine you’re developing an application for both Windows and MacOS platforms. You want your codebase to be as platform-independent as possible so it can easily support new operating systems without having to modify a lot of existing code. The Abstract Factory pattern allows you to do this by providing a way to encapsulate a group of individual factories that have a common theme, i.e., they create products belonging to the same product family.
9+
In some programs, you need to support multiple sets of objects that should work together—for example, GUI elements for different operating systems like Windows and macOS. Instead of scattering `if-else` or `switch` logic across your code to decide which object to create, you can use an abstract factory to centralize and simplify that decision. This makes it easier to support new platforms or themes in the future.
1010

1111
## When to Use It
1212

13-
The Abstract Factory is all about decoupling the client from concrete implementations and making it work with any pre-configured factory or set of related factories without having to modify your codebase. This pattern should be used when you want a run-time pluggability of products, i.e., you need an application that can use instances of different families of products.
13+
Use the Abstract Factory pattern when:
14+
15+
* Your code needs to work with multiple families of related objects (e.g., a GUI that supports different platforms).
16+
* You want to avoid hard-coding specific class names throughout your codebase.
17+
* You want to provide a way to switch between different product families at runtime.
1418

1519
## When NOT to Use It
1620

17-
The Abstract Factory is not suitable for systems where the client needs to work with only one product family at a time. If your system deals with just two or three classes related by a common interface, it might be simpler and more straightforward to instantiate these classes directly using constructor injection.
21+
Avoid this pattern if:
22+
23+
* Your app only uses a small number of related classes, and adding abstraction would complicate the design.
24+
* You don’t need to support multiple interchangeable families of products.
1825

1926
## How It Works
2027

21-
In an Abstract Factory pattern, there are four main components:
28+
The pattern includes four main parts:
2229

23-
1. **Abstract Products** - These are the interfaces that define operations for different types of products. In our example, this would include `Button` and `Checkbox` abstract classes.
24-
2. **Concrete Products** - These are the actual implementations of the Abstract Products. We have separate concrete product classes for each operating system (Windows and MacOS).
25-
3. **Abstract Factory** - This is an interface that provides methods to create different types of products. In our example, this would be `GUIFactory`.
26-
4. **Concrete Factories** - These are the implementations of Abstract Factory interfaces. Each Concrete Factory corresponds to a specific operating system and creates concrete product objects belonging to that family.
30+
1. **Abstract Products** – Interfaces that define common operations (e.g., `Button`, `Checkbox`).
31+
2. **Concrete Products** – Actual implementations of those interfaces for each product family (e.g., `WindowsButton`, `MacButton`).
32+
3. **Abstract Factory** – An interface for creating each type of product (e.g., `GUIFactory`).
33+
4. **Concrete Factories** – Implementations of the abstract factory for each product family (e.g., `WindowsFactory`, `MacFactory`).
2734

2835
## Real-World Analogy
2936

30-
Imagine you're at a supermarket. You have different sections for fruits, vegetables, dairy products etc., each with their own counters and checkout lines. Now imagine if the supermarket had a way of dynamically managing these sections based on your shopping preferences (e.g., vegan or gluten-free). That's similar to how an Abstract Factory pattern allows for dynamic management of product families in an application, based on runtime configuration or user preference.
37+
Think of a supermarket that adapts to your dietary needs. If you choose "vegan," you’re directed to vegan fruit, vegan dairy alternatives, and vegan baked goods—all tailored to that lifestyle. Similarly, an abstract factory delivers a full set of products matched to a specific family or context, without mixing them up.
3138

3239
## Simplified Example
3340

34-
Here is a simplified example:
41+
Here’s a basic structure in Python:
3542

3643
```python
37-
class ProductA:
44+
from abc import ABC, abstractmethod
45+
46+
# Abstract Product
47+
class ProductA(ABC):
48+
@abstractmethod
3849
def do_something(self) -> str:
3950
pass
4051

52+
# Concrete Products
4153
class ConcreteProductA1(ProductA):
4254
def do_something(self) -> str:
4355
return "Implementation of Product A1"
@@ -47,8 +59,14 @@ class ConcreteProductA2(ProductA):
4759
return "Implementation of Product A2"
4860
```
4961

50-
In this example, `ProductA` is the Abstract Product and `ConcreteProductA1` and `ConcreteProductA2` are its concrete implementations.
62+
In this example:
63+
64+
* `ProductA` is the abstract product.
65+
* `ConcreteProductA1` and `ConcreteProductA2` are platform- or theme-specific implementations.
66+
67+
In a full version, you’d also have a factory interface and one or more factory classes to create the products.
5168

52-
## See Also
69+
## Learn More
5370

54-
The corresponding Python file for this lesson can be found [here](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/abstract_factory.py).
71+
You can view the complete implementation in Python here:
72+
[Abstract Factory on GitHub](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/abstract_factory.py)

docs/creational_borg.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,35 @@
22

33
## Purpose
44

5-
The Borg pattern creates objects that share the same state without having to inherit from a common class. It helps simplify programs by allowing different objects to access and update shared data.
5+
The Borg pattern allows multiple instances of a class to share the same internal state. Unlike the Singleton pattern—which restricts you to a single instance—Borg lets you create many instances that all behave as if they share one brain.
66

77
## The Problem It Solves
88

9-
In complex systems, it's often hard to manage shared data across multiple objects. Normally, you’d use inheritance or global variables to do this, but those approaches can be messy or limiting. The Borg pattern solves this by letting every instance share the same internal state.
9+
In some applications, you need objects to share data—for example, configuration settings or a pool of reusable resources. While global variables or singletons can do this, they often come with drawbacks like tight coupling or reduced flexibility. Borg offers an alternative by letting multiple objects share state without being the same instance.
1010

1111
## When to Use It
1212

13-
Use the Borg pattern when you need several objects to share and update the same data. For example, if you have many parts of a program that need to access the same settings or manage the same pool of resources, Borg can help keep things in sync without using global variables or inheritance.
13+
Use the Borg pattern when:
14+
15+
* You want multiple instances to share the same internal data.
16+
* You need a lightweight way to synchronize state across objects.
17+
* You want to avoid singletons but still centralize shared information.
1418

1519
## When NOT to Use It
1620

17-
Don’t use the Borg pattern if each object should keep its own separate data. Also, if you’re working in a multithreaded environment and need to prevent unexpected changes, Borg might not be the right choice—it's not designed to be thread-safe.
21+
Avoid the Borg pattern if:
22+
23+
* Each object should have its own unique state.
24+
* You're working in a multithreaded environment—Borg is not thread-safe without extra precautions.
25+
* You're storing sensitive data that must be isolated per instance.
1826

1927
## How It Works
2028

21-
The Borg class stores all shared data in a single dictionary called `_shared_state`. When you create a new Borg object, it links its internal data (`__dict__`) to this shared dictionary. So any change made by one object shows up in all others.
29+
The Borg pattern links every instance’s `__dict__` (its storage for instance variables) to a class-level shared dictionary. So any change made through one instance is immediately visible to all others.
2230

2331
## Real-World Analogy
2432

25-
Think of a team of scientists using a shared notebook. Each scientist (object) may have their own pen (methods), but they all write in the same notebook (shared data). Whatever one writes, everyone else can see and change.
33+
Imagine several scientists using a single shared notebook. Each scientist can write or read from it, but there’s only one notebook. Whatever one scientist writes, the others will see—it’s a shared workspace for data.
2634

2735
## Simplified Example
2836

@@ -37,10 +45,13 @@ class Borg:
3745
b1 = Borg()
3846
b2 = Borg()
3947

40-
b1.x = 10 # Setting a value through one instance...
41-
print(b2.x) # ...automatically affects the other.
48+
b1.x = 10 # Assigns x through one instance...
49+
print(b2.x) # ...and it’s immediately visible in the other.
4250
```
4351

52+
In this example, both `b1` and `b2` have access to the same data, even though they're separate instances.
53+
4454
## Learn More
4555

46-
You can see the full Borg pattern example on GitHub [here](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/borg.py)
56+
You can explore the full implementation here:
57+
[Borg Pattern on GitHub](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/borg.py)

docs/creational_builder.md

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,117 @@
11
# The Builder Pattern (Creational)
22

3-
## Intent
3+
## Purpose
44

5-
The Builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. It allows constructing complex objects step by step.
5+
The Builder pattern helps you construct complex objects step by step. It separates the construction process from the final product, making it easier to create different versions or configurations of the same type of object.
66

7-
## Problem It Solves
7+
## The Problem It Solves
88

9-
Imagine you are building a complex house, say with multiple rooms and furniture. You don't want to build everything at once because it can take forever and your budget is tight. Instead, you start with the foundation (maybe just the basement), then gradually add more as you need them. This process of constructing an object step by step is what the Builder pattern solves for.
9+
Sometimes an object is too complex to create in one step—like building a house with a foundation, walls, windows, and furniture. You might want different versions of the house, such as a basic layout or a luxury model. The Builder pattern lets you construct such objects gradually, using a consistent process, while still allowing flexibility in the final result.
1010

1111
## When to Use It
1212

13-
The Builder pattern should be used when:
13+
Use the Builder pattern when:
1414

15-
1. You want your code to be able to create different representations of some product (for example, stone and wooden houses).
16-
2. The construction process must allow different representations for the product that are implemented in various subclasses.
17-
3. You need a simple interface to create complex objects.
15+
* You need to build complex objects with many parts.
16+
* You want to reuse the same construction process for different representations.
17+
* You want to isolate the construction logic from the actual product.
1818

1919
## When NOT to Use It
2020

21-
The Builder pattern is not suitable when:
21+
Avoid this pattern if:
2222

23-
1. The object creation algorithm can be defined once and does not get changed often.
24-
2. The client code should be simplified, without being overly complicated by the builder pattern.
25-
3. You need a simple interface that creates only complex objects.
23+
* The object is simple and doesn’t require step-by-step construction.
24+
* You only ever need one version of the object, and its structure rarely changes.
25+
* Adding builders introduces unnecessary complexity for your use case.
2626

2727
## How It Works
2828

29-
The Builder pattern works with four roles: Product, Builder, Director, and Client.
29+
The pattern has four key components:
3030

31-
1. **Product**: This is the object we are building. In our house example, it's the entire house.
32-
2. **Builder**: This defines an interface for creating parts of a product. In our house example, this could be adding rooms or furniture to the house.
33-
3. **Director**: This constructs an object using the Builder interface. It knows what part to build and in which order. In our house example, it would decide when to start building the basement first before moving on to add rooms later.
34-
4. **Client**: The client is responsible for creating a builder object, setting its properties, and running the construction process.
31+
1. **Product** – The final object being built (e.g., a house).
32+
2. **Builder** – Defines methods to build each part of the product (e.g., walls, windows).
33+
3. **ConcreteBuilder** – Implements the builder’s steps to build and assemble parts of the product.
34+
4. **Director** – Controls the construction process and calls builder methods in a specific order.
35+
36+
The **Client** sets up the builder and director, then triggers the construction.
3537

3638
## Real-World Analogy
3739

38-
Think of the Builder pattern as a blueprint for your house. You start with the foundation (reset), then gradually add more components like walls, doors, windows, etc., one by one. The director is like a builder guide who knows exactly when to build each part and in what order.
40+
Think of building a house. You use a blueprint (the builder interface), hire a contractor (the concrete builder), and follow a schedule (the director) to build it step by step. The builder adds pieces like the foundation, walls, and roof. The director decides what to build first and when.
3941

4042
## Simplified Example
4143

42-
Here's a simplified example of how you might use it:
43-
4444
```python
45+
# Builder interface
46+
class Builder(ABC):
47+
@abstractmethod
48+
def build_part_a(self): pass
49+
@abstractmethod
50+
def build_part_b(self): pass
51+
@abstractmethod
52+
def get_product(self): pass
53+
54+
# Concrete builder
55+
class ConcreteBuilder(Builder):
56+
def __init__(self):
57+
self.reset()
58+
59+
def reset(self):
60+
self._product = Product()
61+
62+
def build_part_a(self):
63+
self._product.add("PartA")
64+
65+
def build_part_b(self):
66+
self._product.add("PartB")
67+
68+
def get_product(self):
69+
product = self._product
70+
self.reset()
71+
return product
72+
73+
# Product
74+
class Product:
75+
def __init__(self):
76+
self.parts = []
77+
78+
def add(self, part):
79+
self.parts.append(part)
80+
81+
def list_parts(self):
82+
return ", ".join(self.parts)
83+
84+
# Director
85+
class Director:
86+
def __init__(self, builder: Builder):
87+
self._builder = builder
88+
89+
def build_minimal_viable_product(self):
90+
self._builder.build_part_a()
91+
92+
def build_full_featured_product(self):
93+
self._builder.build_part_a()
94+
self._builder.build_part_b()
95+
96+
# Example usage
4597
builder = ConcreteBuilder()
4698
director = Director(builder)
4799

48-
director.build_minimal_viable_product() # Builds only part A
49-
print("Minimal product:", builder.get_product().list_parts())
100+
director.build_minimal_viable_product()
101+
print("Minimal product:", builder.get_product().list_parts())
50102

51-
director.build_full_featured_product() # Builds parts A and B
103+
director.build_full_featured_product()
52104
print("Full product:", builder.get_product().list_parts())
53105
```
54106

55-
In this example, `ConcreteBuilder` is the Builder that knows how to build a complex object (the house), `Director` guides the construction process by calling the appropriate building steps in sequence, and the client code creates a ConcreteBuilder object, sets its properties, and runs the construction process.
107+
### Output
108+
109+
```text
110+
Minimal product: PartA
111+
Full product: PartA, PartB
112+
```
56113

57-
## See Also
114+
## Learn More
58115

59-
You can find more details about this pattern [here](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/builder.py).
116+
For a full implementation and examples, check:
117+
[Builder Pattern on GitHub](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/creational/builder.py)

0 commit comments

Comments
 (0)