1 Overview
In object-oriented programming (OOP), instantiation is rarely just a call to new. As a code-base grows, dependencies become tangled and requirements shift. Creational Patterns hide construction details, provide controlled extension points, and reduce coupling between client code and concrete classes.
| Pattern | Core Idea | Typical Use Cases | 
|---|---|---|
| Singleton | Guarantee exactly one instance and offer global access | Configuration, DB connection pool, logging | 
| Factory Method | Delegate creation to sub-classes | Frameworks where plug-ins decide the product type | 
| Abstract Factory | Produce a family of related objects | Cross-platform UI widgets, multi-DB drivers | 
| Builder | Separate construction steps from representation | Complex objects with many optional parts | 
| Prototype | Clone an existing object instead of rebuilding | Expensive setup, repeated configurations | 
2 Pattern Deep-Dive
2.1 Singleton
1. Definition
Ensure a single object of a class exists and provide a global access point to it.
2. Problem Solved
- Resources that must remain unique: config, loggers, caches.
- Prevent wasted memory or inconsistent state from multiple instances.
3. How It Works
- Private constructor (or guarded __new__).
- Store the unique instance in a static field.
- Expose a public accessor such as getInstance().
4. Pros / Cons
| Pros | Cons | 
|---|---|
| Saves resources, keeps state consistent | Hinders unit tests, tightly couples code | 
| Easy to reach from anywhere | Can be over-used → “God object” | 
| Lazy or eager initialization possible | Must be thread-safe in multithreaded apps | 
5. Example (Python)
from threading import Lock
class Logger:
    _instance = None
    _lock = Lock()
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:          # double-checked
                    cls._instance = super().__new__(cls)
                    cls._instance._setup()
        return cls._instance
    def _setup(self):
        self._file = open("app.log", "a")
    def log(self, msg: str):
        self._file.write(msg + "\n")
2.2 Factory Method
1. Definition
Define an interface for creating an object, but let sub-classes decide which concrete class to instantiate.
2. Problem Solved
A framework cannot foresee every concrete class a plug-in might need, yet wants a standard way to obtain objects.
3. How It Works
- A Creatordeclaresfactory_method() → Product.
- Each ConcreteCreatoroverrides it to return its ownConcreteProduct.
4. Pros / Cons
| Pros | Cons | 
|---|---|
| Open/Closed: add products without changing core | More classes to maintain | 
| Removes dependency on concrete classes | Creation logic scattered across sub-classes | 
5. Example (Python)
class Document: ...
class PdfDocument(Document): ...
class WordDocument(Document): ...
class Application:
    def create_document(self) -> Document:      # Factory Method
        raise NotImplementedError
    def open(self):
        doc = self.create_document()
        print(f"Opened {doc.__class__.__name__}")
class PdfApp(Application):
    def create_document(self):
        return PdfDocument()
class WordApp(Application):
    def create_document(self):
        return WordDocument()
app = PdfApp()
app.open()   # → Opened PdfDocument
2.3 Abstract Factory
1. Definition
Provide an interface for creating entire families of related objects without specifying their concrete classes.
2. Problem Solved
- Cross-platform GUI: WindowsButton vs. MacButton, yet client code should stay platform-agnostic.
- Keep products within the same family compatible.
3. How It Works
- AbstractFactorydeclares methods like- create_button()and- create_scrollbar().
- ConcreteFactoryWindows/- ConcreteFactoryMacimplement them, returning platform-specific widgets.
4. Pros / Cons
| Pros | Cons | 
|---|---|
| Ensures product consistency | Harder to add a new product type (every factory changes) | 
| Swap whole families easily | Increases class count | 
| Insulates client from concrete classes | 
5. Example (Python)
class Button: ...
class ScrollBar: ...
class WindowsButton(Button): ...
class MacButton(Button): ...
class AbstractFactory:
    def create_button(self) -> Button: ...
    def create_scrollbar(self) -> ScrollBar: ...
class WindowsFactory(AbstractFactory):
    def create_button(self):   return WindowsButton()
    def create_scrollbar(self): ...
class MacFactory(AbstractFactory):
    def create_button(self):   return MacButton()
    # ...
def render_ui(factory: AbstractFactory):
    btn = factory.create_button()
    sb  = factory.create_scrollbar()
    # use btn and sb
factory = WindowsFactory() if platform == "win32" else MacFactory()
render_ui(factory)
2.4 Builder
1. Definition
Separate the construction process of a complex object from its final representation so the same steps can yield different results.
2. Problem Solved
- Objects with many optional parameters lead to telescoping constructors.
- Need to reuse an assembly sequence across varied configurations.
3. How It Works
- Directororchestrates build steps.
- Builderdefines steps (- add_cheese,- add_sauce, …).
- ConcreteBuilderstores interim state and emits the product via- build().
4. Pros / Cons
| Pros | Cons | 
|---|---|
| Produces immutable finished objects | Added complexity for simple objects | 
| Fluent, readable creation syntax | Requires extra Builder/Director classes | 
| Same algorithm → multiple variants | 
5. Example (Python, fluent style)
class Burger:
    def __init__(self, size, cheese=False, bacon=False):
        self.size, self.cheese, self.bacon = size, cheese, bacon
    def __repr__(self):
        return f"Burger(size={self.size}, cheese={self.cheese}, bacon={self.bacon})"
class BurgerBuilder:
    def __init__(self): self.reset()
    def reset(self):     self._size = 1; self._cheese = False; self._bacon = False; return self
    def size(self, s):   self._size = s; return self
    def cheese(self):    self._cheese = True; return self
    def bacon(self):     self._bacon  = True; return self
    def build(self):     return Burger(self._size, self._cheese, self._bacon)
burger = BurgerBuilder().size(2).cheese().bacon().build()
# → Burger(size=2, cheese=True, bacon=True)
2.5 Prototype
1. Definition
Create new objects by cloning an existing prototype, rather than instantiating from scratch.
2. Problem Solved
- Initialization is costly (I/O, validation, network).
- Desire to duplicate complex configuration quickly.
3. How It Works
- Prototype implements clone()(shallow or deep).
- Client calls clone()to obtain a copy and may tweak it.
4. Pros / Cons
| Pros | Cons | 
|---|---|
| Faster than re-initialization | Must ensure deep copy to avoid shared state | 
| Preserve intricate config, then diverge | Managing prototype lifecycle | 
| Removes dependence on concrete class | 
5. Example (Python)
import copy
class Shape:
    def __init__(self, color): self.color = color
    def clone(self):
        return copy.deepcopy(self)
circle = Shape("red")
blue_circle = circle.clone()
blue_circle.color = "blue"
3 Conclusion
Creational Patterns are foundational:
- Open/Close Gatekeepers – conceal construction logic, yet stay open to new products.
- Separation of Concerns – distinguish what the client wants from how it is built.
- Maintainability & Testability – enable dependency inversion, mocking, and clean architecture.
- Scalability – frameworks, plug-ins, and micro-services routinely combine multiple creational patterns to stay tractable over time.
 
        
        ![Lession 2: CREATIONAL PATTERNS – CRAFTING OBJECTS WITH FLEXIBILITY [Detailed Example]](/media/images/TEKTIAS_4.2e16d0ba.fill-1000x500.png) 
        