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 Creator declares factory_method() → Product.
  • Each ConcreteCreator overrides it to return its own ConcreteProduct.

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

  • AbstractFactory declares methods like create_button() and create_scrollbar().
  • ConcreteFactoryWindows / ConcreteFactoryMac implement 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

  • Director orchestrates build steps.
  • Builder defines steps (add_cheese, add_sauce, …).
  • ConcreteBuilder stores 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.

💬 Bình luận