@duongital

Design Patterns in Programming

Some design patterns in OOP languages: Java, C++... and functional ones: Javascript, Python...

functional programming

closure

A closure is when a function “remembers” variables from outside its own scope. It lets inner functions access variables from an outer function even after the outer one has finished running.

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2

currying

Currying transforms a function with multiple arguments into a sequence of functions, each taking one argument.

def add(a):
    def inner(b):
        return a + b
    return inner

add5 = add(5)
print(add5(3))  # 8

object oriented programming

oop - creation

factory method

class Generator:
    def generate(self, type):
        pass

class FactoryNike(Generator):
    def generate(self, type):
        return f"Nike {type}"

factory1 = FactoryNike()
print(factory1.generate("ao_thun"))

abstract factory

class KingdomFactory:
    def create_king(self): pass
    def create_army(self): pass
    def create_castle(self): pass

builder

class Burger:
    def __init__(self):
        self.bread = None
        self.meat = None

class BurgerBuilder:
    def __init__(self):
        self.burger = Burger()

    def set_bread(self, bread):
        self.burger.bread = bread
        return self

    def set_meat(self, meat):
        self.burger.meat = meat
        return self

    def build(self):
        return self.burger

prototype

import copy

class Sheep:
    def clone(self):
        return copy.deepcopy(self)

singleton

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

oop - struct

adapter

class OldPrinter:
    def print_old(self):
        print("Old Printer")

class PrinterAdapter:
    def __init__(self):
        self.old_printer = OldPrinter()

    def print(self):
        self.old_printer.print_old()

bridge

class DrawAPI:
    def draw_circle(self):
        pass

class RedCircle(DrawAPI):
    def draw_circle(self):
        print("Red Circle")

class Circle:
    def __init__(self, draw_api):
        self.draw_api = draw_api

    def draw(self):
        self.draw_api.draw_circle()

composite

Treats individual objects and groups of objects the same way.

class Component:
    def operation(self):
        pass

class Leaf(Component):
    def __init__(self, name):
        self.name = name

    def operation(self):
        print(f"Leaf: {self.name}")

class Composite(Component):
    def __init__(self, name):
        self.name = name
        self.children = []

    def add(self, component):
        self.children.append(component)

    def operation(self):
        print(f"Composite: {self.name}")
        for child in self.children:
            child.operation()

# Usage
leaf1 = Leaf("A")
leaf2 = Leaf("B")
tree = Composite("Root")
tree.add(leaf1)
tree.add(leaf2)

sub_tree = Composite("Sub")
sub_tree.add(Leaf("C"))
tree.add(sub_tree)

tree.operation()

decorator

Adds behavior to objects at runtime without changing their class.

class Coffee:
    def cost(self):
        return 5

class MilkDecorator:
    def __init__(self, coffee):
        self._coffee = coffee

    def cost(self):
        return self._coffee.cost() + 2

class SugarDecorator:
    def __init__(self, coffee):
        self._coffee = coffee

    def cost(self):
        return self._coffee.cost() + 1

# Usage
coffee = Coffee()
coffee_with_milk = MilkDecorator(coffee)
coffee_with_milk_and_sugar = SugarDecorator(coffee_with_milk)

print("Total cost:", coffee_with_milk_and_sugar.cost())  # Output: 8

facade

Simplifies access to a large system by providing a unified interface.

class CPU:
    def freeze(self):
        print("CPU freeze")

    def execute(self):
        print("CPU executing")

class Memory:
    def load(self, position, data):
        print(f"Loading {data} into memory at {position}")

class HardDrive:
    def read(self, sector, size):
        return f"Data from sector {sector}"

class ComputerFacade:
    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.hard_drive = HardDrive()

    def start(self):
        self.cpu.freeze()
        data = self.hard_drive.read(100, 20)
        self.memory.load(0, data)
        self.cpu.execute()

# Usage
computer = ComputerFacade()
computer.start()

flyweight

Minimizes memory usage by sharing data between similar objects.

class TreeType:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def draw(self, x, y):
        print(f"Drawing {self.name} tree in {self.color} at ({x}, {y})")

class TreeFactory:
    _tree_types = {}

    @staticmethod
    def get_tree_type(name, color):
        key = (name, color)
        if key not in TreeFactory._tree_types:
            TreeFactory._tree_types[key] = TreeType(name, color)
        return TreeFactory._tree_types[key]

class Tree:
    def __init__(self, x, y, tree_type):
        self.x = x
        self.y = y
        self.tree_type = tree_type

    def draw(self):
        self.tree_type.draw(self.x, self.y)

# Usage
trees = []
for i in range(5):
    tree_type = TreeFactory.get_tree_type("Oak", "Green")
    trees.append(Tree(i, i*2, tree_type))

for tree in trees:
    tree.draw()

proxy

Controls access to an object, adding a layer like logging or validation.

class RealDatabase:
    def query(self):
        print("Querying the real database...")

class ProxyDatabase:
    def __init__(self):
        self._real_db = None

    def query(self):
        print("Logging: Attempt to access database")
        if self._real_db is None:
            self._real_db = RealDatabase()
        self._real_db.query()

# Usage
db = ProxyDatabase()
db.query()
db.query()

oop - behavior

chain of responsibility

Passes request along a chain of handlers.

class Handler:
    def __init__(self, successor=None):
        self.successor = successor

    def handle(self, request):
        if self.successor:
            return self.successor.handle(request)

class ConcreteHandler1(Handler):
    def handle(self, request):
        if request < 10:
            return f"Handled by Handler1: {request}"
        return super().handle(request)

class ConcreteHandler2(Handler):
    def handle(self, request):
        if request < 20:
            return f"Handled by Handler2: {request}"
        return super().handle(request)

# Usage
handler = ConcreteHandler1(ConcreteHandler2())
print(handler.handle(5))
print(handler.handle(15))

command

Encapsulates a request as an object.

class Command:
    def execute(self):
        pass

class Light:
    def turn_on(self):
        print("Light is ON")

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_on()

class RemoteControl:
    def submit(self, command):
        command.execute()

# Usage
light = Light()
command = LightOnCommand(light)
remote = RemoteControl()
remote.submit(command)

iterator

Provides a way to access elements of a collection without exposing the underlying structure.

class MyIterator:
    def __init__(self, collection):
        self._collection = collection
        self._index = 0

    def __next__(self):
        if self._index < len(self._collection):
            value = self._collection[self._index]
            self._index += 1
            return value
        raise StopIteration

class MyCollection:
    def __init__(self):
        self.items = []

    def __iter__(self):
        return MyIterator(self.items)

# Usage
col = MyCollection()
col.items.extend([1, 2, 3])
for item in col:
    print(item)

mediator

Centralizes complex communication between objects.

class Mediator:
    def notify(self, sender, event):
        pass

class ConcreteMediator(Mediator):
    def __init__(self, comp1, comp2):
        self.comp1 = comp1
        self.comp2 = comp2
        self.comp1.set_mediator(self)
        self.comp2.set_mediator(self)

    def notify(self, sender, event):
        if event == "A":
            print("Mediator reacts to A and triggers B")
            self.comp2.do_b()

class Component:
    def set_mediator(self, mediator):
        self.mediator = mediator

class Component1(Component):
    def do_a(self):
        print("Component1 does A")
        self.mediator.notify(self, "A")

class Component2(Component):
    def do_b(self):
        print("Component2 does B")

# Usage
c1 = Component1()
c2 = Component2()
mediator = ConcreteMediator(c1, c2)
c1.do_a()

memento

Captures and restores an object’s state.

class Memento:
    def __init__(self, state):
        self._state = state

    def get_saved_state(self):
        return self._state

class Originator:
    def __init__(self):
        self._state = ""

    def set(self, state):
        print(f"Setting state to {state}")
        self._state = state

    def save(self):
        return Memento(self._state)

    def restore(self, memento):
        self._state = memento.get_saved_state()
        print(f"Restored to {self._state}")

# Usage
originator = Originator()
originator.set("State1")
memento = originator.save()
originator.set("State2")
originator.restore(memento)

observer

Notifies dependents when an object changes.

class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def notify(self, message):
        for obs in self._observers:
            obs.update(message)

class Observer:
    def update(self, message):
        print(f"Observer received: {message}")

# Usage
subject = Subject()
observer1 = Observer()
observer2 = Observer()
subject.attach(observer1)
subject.attach(observer2)
subject.notify("Event occurred!")

state

Lets an object alter its behavior when its state changes.

class State:
    def handle(self):
        pass

class StateA(State):
    def handle(self):
        print("State A behavior")

class StateB(State):
    def handle(self):
        print("State B behavior")

class Context:
    def __init__(self, state):
        self.state = state

    def request(self):
        self.state.handle()

# Usage
context = Context(StateA())
context.request()
context.state = StateB()
context.request()

strategy

Lets a class change its behavior at runtime.

class Strategy:
    def execute(self, a, b):
        pass

class Add(Strategy):
    def execute(self, a, b):
        return a + b

class Subtract(Strategy):
    def execute(self, a, b):
        return a - b

class Context:
    def __init__(self, strategy):
        self.strategy = strategy

    def do_operation(self, a, b):
        return self.strategy.execute(a, b)

# Usage
context = Context(Add())
print(context.do_operation(5, 3))
context = Context(Subtract())
print(context.do_operation(5, 3))

template method

Defines the skeleton of an algorithm and lets subclasses override steps.

class AbstractClass:
    def template_method(self):
        self.step1()
        self.step2()

    def step1(self):
        print("Step 1")

    def step2(self):
        raise NotImplementedError

class ConcreteClass(AbstractClass):
    def step2(self):
        print("Step 2 overridden")

# Usage
obj = ConcreteClass()
obj.template_method()

visitor

Lets you add new operations to existing object structures.

class Visitor:
    def visit_element_a(self, element):
        pass

    def visit_element_b(self, element):
        pass

class Element:
    def accept(self, visitor):
        pass

class ElementA(Element):
    def accept(self, visitor):
        visitor.visit_element_a(self)

class ElementB(Element):
    def accept(self, visitor):
        visitor.visit_element_b(self)

class ConcreteVisitor(Visitor):
    def visit_element_a(self, element):
        print("Processing Element A")

    def visit_element_b(self, element):
        print("Processing Element B")

# Usage
elements = [ElementA(), ElementB()]
visitor = ConcreteVisitor()
for elem in elements:
    elem.accept(visitor)