1. functional programming
Functional programming emphasizes pure functions, immutability, and function composition to build predictable and maintainable code.
graph LR
subgraph FP["Functional Programming Patterns"]
Closure["Closure<br/>Captures outer scope"]
Currying["Currying<br/>Partial application"]
Composition["Function Composition<br/>Combine functions"]
HOF["Higher-Order Functions<br/>Functions as arguments"]
Immutability["Immutability<br/>No side effects"]
PureFunctions["Pure Functions<br/>Predictable output"]
end
Closure -.->|"Use for"| C1["Encapsulate state<br/>Create factories"]
Currying -.->|"Use for"| CU1["Reusable functions<br/>Partial application"]
Composition -.->|"Use for"| CO1["Build complex logic<br/>Pipeline operations"]
HOF -.->|"Use for"| H1["Map/filter/reduce<br/>Function transformers"]
Immutability -.->|"Use for"| I1["Thread safety<br/>Predictable state"]
PureFunctions -.->|"Use for"| P1["Testable code<br/>No side effects"]
style Closure fill:#ffcccc
style Currying fill:#ccffcc
style Composition fill:#ccccff
style HOF fill:#ffffcc
style Immutability fill:#ffccff
style PureFunctions fill:#ccffff
1.1 closure
Like a backpack that remembers what you packed in it - even when you’re far from home, you still have access to everything inside.
In this code, the inner function remembers the count variable from outer even after outer finishes running, so each time you call counter() it keeps counting up.
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
1.2 currying
Like a vending machine where you first select the category (snacks), then the specific item (chips), then the size (large) - one choice at a time.
In this code, add(5) creates a new function that remembers 5, and when you call add5(3) it adds 5+3=8 - you’re breaking down one big choice into smaller steps.
def add(a):
def inner(b):
return a + b
return inner
add5 = add(5)
print(add5(3)) # 8
# Practical example: Custom loggers
def create_logger(prefix):
def log(level):
def message(msg):
print(f"[{prefix}] {level}: {msg}")
return message
return log
app_logger = create_logger("APP")
error_log = app_logger("ERROR")
info_log = app_logger("INFO")
error_log("Database connection failed") # [APP] ERROR: Database connection failed
info_log("Server started") # [APP] INFO: Server started
1.3 higher-order functions
Like a chef who can use different cooking methods (baking, frying, grilling) on ingredients - the chef takes the cooking method as instructions and applies it.
In this code, map takes a function (lambda x: x**2) and applies it to every number in the list, filter uses a function to pick which items to keep, and reduce combines items using a function.
# Map, filter, reduce examples
numbers = [1, 2, 3, 4, 5]
# Map: Transform each element
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Filter: Select elements
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# Reduce: Combine elements
from functools import reduce
sum_all = reduce(lambda x, y: x + y, numbers)
print(sum_all) # 15
# Custom higher-order function
def apply_twice(func, value):
return func(func(value))
def add_three(x):
return x + 3
result = apply_twice(add_three, 10)
print(result) # 16 (10 + 3 + 3)
1.4 function composition
Like an assembly line where each station does one simple task - the product moves through each station and gets transformed step by step.
In this code, compose takes multiple functions and chains them together, so pipeline(3) does add_one(3)=4, then double(4)=8, then square(8)=64, passing the result through each function.
def compose(*functions):
"""Compose functions from right to left"""
def inner(arg):
result = arg
for func in reversed(functions):
result = func(result)
return result
return inner
# Simple functions
def add_one(x):
return x + 1
def double(x):
return x * 2
def square(x):
return x ** 2
# Compose them
pipeline = compose(square, double, add_one)
print(pipeline(3)) # ((3 + 1) * 2)^2 = 64
# Practical example: Data processing
def remove_spaces(text):
return text.replace(" ", "")
def to_uppercase(text):
return text.upper()
def add_prefix(text):
return f"PROCESSED: {text}"
process_text = compose(add_prefix, to_uppercase, remove_spaces)
print(process_text("hello world")) # PROCESSED: HELLOWORLD
1.5 pure functions & immutability
Like a calculator that always gives you the same answer for 2+2=4, and doesn’t change anything else in the world when you use it.
In this code, pure_add always returns 3 when you pass (1,2) and doesn’t change anything else, but impure_add changes the global total variable which makes it unpredictable and harder to debug.
# Pure function - predictable, no side effects
def pure_add(a, b):
return a + b
# Impure function - modifies external state
total = 0
def impure_add(a):
global total
total += a
return total
# Working with immutable data
def add_item_immutable(items, new_item):
"""Returns new list instead of modifying original"""
return items + [new_item]
original = [1, 2, 3]
new_list = add_item_immutable(original, 4)
print(original) # [1, 2, 3] - unchanged
print(new_list) # [1, 2, 3, 4]
# Immutable updates for dictionaries
def update_user_immutable(user, **updates):
"""Return new dict with updates"""
return {**user, **updates}
user = {"name": "John", "age": 30}
updated_user = update_user_immutable(user, age=31, city="NYC")
print(user) # {'name': 'John', 'age': 30} - unchanged
print(updated_user) # {'name': 'John', 'age': 31, 'city': 'NYC'}
2. object oriented programming
Object-oriented programming organizes code around objects and classes, using encapsulation, inheritance, and polymorphism.
graph TB
subgraph OOP["OOP Design Patterns (Gang of Four)"]
Creation["Creational Patterns<br/>Object creation"]
Structural["Structural Patterns<br/>Object composition"]
Behavioral["Behavioral Patterns<br/>Object interaction"]
end
subgraph CreationalPatterns["Creational (5)"]
Factory["Factory Method"]
AbstractFactory["Abstract Factory"]
Builder["Builder"]
Prototype["Prototype"]
Singleton["Singleton"]
end
subgraph StructuralPatterns["Structural (7)"]
Adapter["Adapter"]
Bridge["Bridge"]
Composite["Composite"]
Decorator["Decorator"]
Facade["Facade"]
Flyweight["Flyweight"]
Proxy["Proxy"]
end
subgraph BehavioralPatterns["Behavioral (11)"]
Chain["Chain of Responsibility"]
Command["Command"]
Iterator["Iterator"]
Mediator["Mediator"]
Memento["Memento"]
Observer["Observer"]
State["State"]
Strategy["Strategy"]
Template["Template Method"]
Visitor["Visitor"]
end
Creation --> CreationalPatterns
Structural --> StructuralPatterns
Behavioral --> BehavioralPatterns
style Creation fill:#ffcccc
style Structural fill:#ccffcc
style Behavioral fill:#ccccff
2.1. oop - creation
Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
graph LR
subgraph Creational["Creational Patterns"]
Factory["Factory Method<br/>Define interface for<br/>creating objects"]
AbstractFactory["Abstract Factory<br/>Create families of<br/>related objects"]
Builder["Builder<br/>Construct complex<br/>objects step by step"]
Prototype["Prototype<br/>Clone existing<br/>objects"]
Singleton["Singleton<br/>Ensure single<br/>instance"]
end
Factory -.->|"Use when"| F1["Multiple object types<br/>Runtime decision"]
AbstractFactory -.->|"Use when"| AF1["Related object<br/>families"]
Builder -.->|"Use when"| B1["Complex object<br/>construction"]
Prototype -.->|"Use when"| P1["Expensive object<br/>creation"]
Singleton -.->|"Use when"| S1["Single shared<br/>instance needed"]
style Factory fill:#ffcccc
style AbstractFactory fill:#ccffcc
style Builder fill:#ccccff
style Prototype fill:#ffffcc
style Singleton fill:#ffccff
2.1.1. factory method
Like a toy factory that can make different types of toys - you tell it what you want, and it creates the right toy for you.
In this code, the FactoryNike class is like a factory that can generate any Nike product (like “ao_thun” which means t-shirt) when you ask for it.
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"))
2.1.2. abstract factory
Like getting a complete LEGO set instead of individual pieces - it gives you a whole family of matching things that work together.
In this code, the KingdomFactory can create a complete kingdom with a king, army, and castle all at once, and they all match the same theme.
class KingdomFactory:
def create_king(self): pass
def create_army(self): pass
def create_castle(self): pass
2.1.3. builder
Like building a custom burger at a restaurant - you choose the bun, then the patty, then the toppings, one step at a time.
In this code, the BurgerBuilder lets you build your burger step by step - first you set the bread, then set the meat, and finally build it all together.
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
2.1.4. prototype
Like using a photocopier to make exact copies of something instead of creating each one from scratch.
In this code, the Sheep class has a clone method that makes an exact copy of the sheep, just like cloning Dolly the sheep in real life.
import copy
class Sheep:
def clone(self):
return copy.deepcopy(self)
2.1.5 singleton
Like having only one sun in the sky - no matter how many times you look up, you always see the same one.
In this code, the Singleton class makes sure that no matter how many times you try to create a new instance, you always get back the same one that was created the first time.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
2.2. oop - struct
Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.
graph LR
subgraph Structural["Structural Patterns"]
Adapter["Adapter<br/>Convert interface"]
Bridge["Bridge<br/>Separate abstraction<br/>from implementation"]
Composite["Composite<br/>Tree structure"]
Decorator["Decorator<br/>Add responsibilities"]
Facade["Facade<br/>Simplified interface"]
Flyweight["Flyweight<br/>Share common state"]
Proxy["Proxy<br/>Control access"]
end
Adapter -.->|"Use when"| A1["Incompatible interfaces<br/>Wrap legacy code"]
Bridge -.->|"Use when"| BR1["Decouple abstraction<br/>from implementation"]
Composite -.->|"Use when"| C1["Part-whole hierarchies<br/>Tree structures"]
Decorator -.->|"Use when"| D1["Add behavior dynamically<br/>Extend functionality"]
Facade -.->|"Use when"| F1["Simplify complex subsystems<br/>Unified interface"]
Flyweight -.->|"Use when"| FL1["Many similar objects<br/>Memory optimization"]
Proxy -.->|"Use when"| P1["Control access<br/>Lazy loading"]
style Adapter fill:#ffcccc
style Bridge fill:#ccffcc
style Composite fill:#ccccff
style Decorator fill:#ffffcc
style Facade fill:#ffccff
style Flyweight fill:#ccffdd
style Proxy fill:#ffddcc
2.2.1. adapter
Like a power plug adapter that lets you use your phone charger in a different country’s wall socket.
In this code, the PrinterAdapter wraps the OldPrinter so you can use its old print_old method with a new simple print method.
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()
2.2.2. bridge
Like separating a remote control from your TV - you can change either one without affecting the other.
In this code, the Circle class can use any DrawAPI (like RedCircle) to draw itself - you can change how it draws without changing the Circle class.
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()
2.2.3. composite
Like a folder on your computer that can contain both files and other folders, and you can treat them all the same way.
In this code, a Leaf is like a single file, and a Composite is like a folder that can hold many Leaves or other Composites - you call operation() on both 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()
2.2.4. decorator
Like adding toppings to ice cream - you start with plain ice cream and keep adding sprinkles, chocolate sauce, and whipped cream on top.
In this code, you start with plain Coffee that costs $5, then wrap it with MilkDecorator (adds $2), then wrap that with SugarDecorator (adds $1) for a total of $8.
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
2.2.5. facade
Like a TV remote with one “power” button that turns on the TV, sound system, and cable box all at once - it makes complicated things simple.
In this code, ComputerFacade hides the complex parts (CPU, Memory, HardDrive) and gives you one simple start() button to turn on the whole computer.
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()
2.2.6. flyweight
Like a library where many people can read the same book instead of everyone buying their own copy - it saves space by sharing.
In this code, TreeFactory creates only one TreeType for “Oak Green” trees, and all 5 Tree objects share that same TreeType instead of each having their own copy.
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()
2.2.7. proxy
Like a security guard who checks your ID before letting you into a building - it controls who can access something.
In this code, ProxyDatabase adds a logging message before accessing the real database, and only creates the RealDatabase when you actually need it (lazy loading).
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()
2.3. oop - behavior
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.
graph LR
subgraph Behavioral["Behavioral Patterns"]
Chain["Chain of Responsibility<br/>Pass request along chain"]
Command["Command<br/>Encapsulate requests"]
Iterator["Iterator<br/>Sequential access"]
Mediator["Mediator<br/>Centralize communication"]
Memento["Memento<br/>Capture/restore state"]
Observer["Observer<br/>Notify dependents"]
State["State<br/>Alter behavior by state"]
Strategy["Strategy<br/>Interchangeable algorithms"]
Template["Template Method<br/>Algorithm skeleton"]
Visitor["Visitor<br/>Add operations"]
end
Chain -.->|"Use when"| CH1["Multiple handlers<br/>Dynamic handler chain"]
Command -.->|"Use when"| CM1["Parameterize operations<br/>Undo/redo functionality"]
Iterator -.->|"Use when"| IT1["Traverse collection uniformly<br/>Hide internal structure"]
Mediator -.->|"Use when"| MD1["Complex object communication<br/>Reduce coupling"]
Memento -.->|"Use when"| MM1["Save/restore state<br/>Undo mechanism"]
Observer -.->|"Use when"| OB1["One-to-many dependency<br/>Event notification"]
State -.->|"Use when"| ST1["Behavior changes with state<br/>Avoid large conditionals"]
Strategy -.->|"Use when"| STR1["Multiple algorithms<br/>Runtime selection"]
Template -.->|"Use when"| TM1["Common algorithm structure<br/>Varying implementation steps"]
Visitor -.->|"Use when"| VS1["Operations on object structure<br/>Add operations easily"]
style Chain fill:#ffcccc
style Command fill:#ccffcc
style Iterator fill:#ccccff
style Mediator fill:#ffffcc
style Memento fill:#ffccff
style Observer fill:#ccffdd
style State fill:#ffddcc
style Strategy fill:#ddffcc
style Template fill:#ccddff
style Visitor fill:#ffddff
2.3.1. chain of responsibility
Like asking for help at school - first you ask your friend, then your teacher, then the principal until someone can answer your question.
In this code, when you handle a request, ConcreteHandler1 tries first (handles if less than 10), and if it can’t, it passes to ConcreteHandler2 (handles if less than 20).
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))
2.3.2. command
Like writing instructions on sticky notes - you can save them, pass them to others, or undo them later.
In this code, LightOnCommand is like a sticky note with instructions to turn on the light - you can pass this command to RemoteControl to execute whenever you want.
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
Like flipping through pages in a book one at a time - you don’t need to know how the book is made, you just turn the page.
In this code, MyIterator lets you loop through items in MyCollection one by one using a for loop, without worrying about how the list is stored inside.
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)
2.3.3. mediator
Like a traffic light that tells all the cars when to stop and go - instead of cars talking to each other, they all listen to one controller.
In this code, ConcreteMediator is like the traffic light - when Component1 does action A, the mediator tells Component2 to do action B instead of them talking directly.
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()
2.3.4. memento
Like saving your game progress - you can go back to that exact moment later if you make a mistake.
In this code, Originator saves its state into a Memento (like a save file), then changes to State2, but can restore back to State1 using the saved memento.
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)
2.3.5. observer
Like subscribing to a YouTube channel - whenever they post a new video, all subscribers get notified automatically.
In this code, Subject is like the YouTube channel and Observers are subscribers - when you call notify(), all attached observers receive the message automatically.
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!")
2.3.6. state
Like a water bottle that acts differently when it’s full (heavy and steady) versus empty (light and easy to crush).
In this code, Context changes its behavior by switching between StateA and StateB - the same request() call does different things depending on which state it’s in.
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()
2.3.7. strategy
Like choosing different ways to get to school - you can walk, bike, or take the bus depending on the weather.
In this code, Context can switch between different strategies (Add or Subtract) - you choose which strategy to use and it performs the calculation that way.
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))
2.3.8. template method
Like a recipe that has the same steps (mix, bake, cool) but you can change the ingredients to make different cakes.
In this code, AbstractClass defines the recipe (template_method does step1 then step2), but ConcreteClass can override step2 to make it do something different.
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()
2.3.9. visitor
Like a doctor visiting patients in a hospital - the doctor can do different things for each type of patient without the patients changing.
In this code, ConcreteVisitor is like the doctor who visits ElementA and ElementB - each element “accepts” the visitor and lets it do different operations on them.
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)