B03 Design Patterns
Abstract Factory
# Example: Abstract Factory
from abc import ABC, abstractmethod
class Button(ABC):
# Abstract interface for buttons
@abstractmethod
def paint(self):
raise NotImplementedError
class WinButton(Button):
# Concrete foo for Windows buttons
def paint(self):
print("WinButton")
class LinuxButton(Button):
def paint(self):
print("LinuxButton")
class Menu(ABC):
# Abstract interface for menus
@abstractmethod
def paint(self):
raise NotImplementedError
class WinMenu(Menu):
# Concrete foo for Windows menus
def paint(self):
print("WindowsMenu")
class LinuxMenu(Menu):
# Concrete foo for Linux menus
def paint(self):
print("LinuxMenu")
class GUIFactory(ABC):
# Abstract factory that declares a set of methods for creating each of the
# products. These methods must return abstract foo types represented by
# the abstract interfaces Button and Menu.
@abstractmethod
def create_button(self) -> Button:
raise NotImplementedError
@abstractmethod
def create_menu(self) -> Menu:
raise NotImplementedError
class WinFactory(GUIFactory):
# The concrete factory for Windows foo variants.
def create_button(self):
return WinButton()
def create_menu(self):
return WinMenu()
class LinuxFactory(GUIFactory):
# The concrete factory for Linux foo variants.
def create_button(self):
return LinuxButton()
def create_menu(self):
return LinuxMenu()
if __name__ == "__main__":
os = input("Select OS: ")
if os == "win":
factory = WinFactory()
elif os == "linux":
factory = LinuxFactory()
else:
raise ValueError("Invalid GUI")
# Create the GUI
button = factory.create_button()
menu = factory.create_menu()
gui = [button, menu]
# Paint the GUI
for item in gui:
item.paint()
Adapter With Composition
# Example: Adapter Pattern (Object Adapter)
class ChannelV1(object):
@staticmethod
def applyConfig():
# method name from actual legacy code in the company
print('Configuration method of the old device class!')
class ChannelV2(object):
@staticmethod
def configure():
print('Configuration method of the new device class!')
class ChannelAdapter(ChannelV1):
# Class adapter (with composition)
def __init__(self, adaptee):
self.adaptee = adaptee
def applyConfig(self):
self.adaptee.configure()
def host_app(channel):
channel.applyConfig()
if __name__ == "__main__":
# Original code using the old service
host_app(channel=ChannelV1())
# Use adapter for the new service adapted to the old interface
host_app(channel=ChannelAdapter(adaptee=ChannelV2()))
Adapter With Inheritance
class ChannelV1(object):
@staticmethod
def applyConfig():
# method name from actual legacy code in the company (Python 2.7)
print('Configuration method of the old device class!')
class ChannelV2(object):
@staticmethod
def configure():
print('Configuration method of the new device class!')
class ChannelAdapter(ChannelV1, ChannelV2):
# Class adapter (with inheritance)
def applyConfig(self):
# The adapter's applyConfig method calls the new class's configure method
self.configure()
def host_app(channel):
# The host_app function works with the ChannelV1 interface
channel.applyConfig()
if __name__ == "__main__":
# Original code using the old service (ChannelV1)
host_app(channel=ChannelV1())
# Use adapter for the new service (ChannelV2) adapted to the old interface (ChannelV1)
host_app(channel=ChannelAdapter())
Bridge
# Example: Bridge Pattern with shapes and colors
class Color(object):
# Interface for Implementation
def __init__(self, name):
self.name = name
def paint(self, shape):
raise NotImplementedError
class Red(Color):
# Concrete implementation for red color
def __init__(self):
super(Red, self).__init__('red')
def paint(self, shape):
print('Painting the {} with red color'.format(shape))
class Blue(Color):
# Concrete implementation for blue color
def __init__(self):
super(Blue, self).__init__('blue')
def paint(self, shape):
print('Painting the {} with blue color'.format(shape))
class Shape(object):
# This is the abstaction used by the client
def __init__(self, color: Color):
self.color = color
def draw(self):
raise NotImplementedError
class Circle(Shape):
# Refinded abstraction for circles
def draw(self):
print('Drawing a circle')
self.color.paint(shape='circle')
class Square(Shape):
# Refined abstraction for squares
def draw(self):
print('Drawing a square')
self.color.paint(shape='square')
class DrawApp(object):
# This is the client class
@staticmethod
def draw(shape: Shape):
shape.draw()
if __name__ == "__main__":
# Draw a red circle
app = DrawApp()
# Draw shapes
app.draw(shape=Circle(color=Red()))
app.draw(shape=Square(color=Blue()))
Builder
# Example: Builder Pattern
from abc import ABC, abstractmethod
class Pizza(object):
def __init__(self):
self.crust = None
self.cheese = None
self.toppings = []
def __str__(self):
return "Crust: {0}\nCheese: {1}\nToppings: {2}".format(
self.crust,
self.cheese,
", ".join(self.toppings)
)
class PizzaBuilderAbc(ABC):
@abstractmethod
def set_crust(self, crust):
pass
@abstractmethod
def add_cheese(self, cheese):
pass
@abstractmethod
def add_topping(self, topping):
pass
@abstractmethod
def build(self):
pass
class MyPizzaBuilder(object):
def __init__(self):
self.pizza = Pizza()
def set_crust(self, crust):
self.pizza.crust = crust
return self
def add_cheese(self, cheese):
self.pizza.cheese = cheese
return self
def add_topping(self, topping):
self.pizza.toppings.append(topping)
return self
def build(self):
return self.pizza
pizza = (
MyPizzaBuilder()
.set_crust("thin")
.add_cheese("mozzarella")
.add_topping("pepperoni")
.add_topping("mushrooms")
.build()
)
print(pizza)
Chain
Chain Of Responsibility
# Example: Chain of Responsibility Pattern
# Handler interface
class Approver(object):
def __init__(self, successor=None):
self.successor = successor
def set_successor(self, successor):
self.successor = successor
def approve(self, purchase):
pass
# Concrete handlers
class TeamLeader(Approver):
def approve(self, purchase):
if purchase <= 1000:
print(f"Team Leader approves the purchase of ${purchase}")
elif self.successor:
self.successor.approve(purchase)
class Manager(Approver):
def approve(self, purchase):
if 1000 < purchase <= 5000:
print(f"Manager approves the purchase of ${purchase}")
elif self.successor:
self.successor.approve(purchase)
class Director(Approver):
def approve(self, purchase):
if 5000 < purchase <= 10000:
print(f"Director approves the purchase of ${purchase}")
elif self.successor:
self.successor.approve(purchase)
class President(Approver):
def approve(self, purchase):
if purchase > 10000:
print(f"President approves the purchase of ${purchase}")
elif self.successor:
self.successor.approve(purchase)
# Client code
if __name__ == "__main__":
# Define the roles in the chain of responsibility
team_leader = TeamLeader()
manager = Manager()
director = Director()
president = President()
# Set the successors
team_leader.set_successor(manager)
manager.set_successor(director)
director.set_successor(president)
# Create purchases
purchases = [500, 1000, 2000, 5000, 10000, 20000]
# Process the purchases
for purchase in purchases:
team_leader.approve(purchase)
Class Factory
# Class factory using the __new__ method
# ------------------------------------------------------------------------------
# Overriding __new__ allows a class to act as a factory. The method returns an
# instance of a specific subclass based on the provided parameters. This
# separates the decision about which object to create from the calling code.
class WindowsCalculator(object):
@staticmethod
def do():
print("Do in Windows Calculator")
class LinuxCalculator(object):
@staticmethod
def do():
print("Do in Linux Calculator")
class Calculator(object):
def __new__(cls, os="windows"):
# An instance of the WindowsCalculator class is returned
if os == "windows":
return object.__new__(WindowsCalculator)
# An instance of the LinuxCalculator class is returned
elif os == "linux":
return object.__new__(LinuxCalculator)
# Invalid operating system
else:
raise ValueError("Invalid operating system")
# Create a new instance of the Calculator class
calc = Calculator("windows")
# Check if the Calculator class is an instance of the WindowsCalculator class
test = isinstance(calc, WindowsCalculator)
print("Is instance of WindowsCalculator? [{}]".format(test))
# Call the do method
calc.do()
Command
# Example: Command Pattern
# Command interface
class Command(object):
def execute(self):
pass
def undo(self):
pass
# Concrete commands
class TypeCommand(Command):
def __init__(self, text, document):
self.text = text
self.document = document
def execute(self):
self.document.add_text(self.text)
def undo(self):
self.document.remove_text(self.text)
# Receiver class
class Document(object):
def __init__(self):
self.content = ""
def add_text(self, text):
self.content += text
def remove_text(self, text):
if text and text in self.content:
self.content = self.content.replace(text, "", 1)
def get_content(self):
return self.content
# Invoker class (TextEditor)
class TextEditor(object):
def __init__(self):
self.history = []
def execute(self, command):
command.execute()
self.history.append(command)
def undo_last_command(self):
if self.history:
command = self.history.pop()
command.undo()
# Client code
if __name__ == "__main__":
document = Document()
editor = TextEditor()
print("Initial content:", document.get_content())
# Type some text
type_action1 = TypeCommand("Hello, ", document)
editor.execute(type_action1)
print("After typing:", document.get_content())
type_action2 = TypeCommand("world!", document)
editor.execute(type_action2)
print("After typing:", document.get_content())
# Undo the last command
editor.undo_last_command()
print("After undo:", document.get_content())
Composite
# Example: Composite pattern
from abc import abstractmethod
class IComponent(object):
# Interface used by both the leaf and composite classes
def __init__(self, name):
self.name = name
@abstractmethod
def execute(self):
raise NotImplementedError
class Leaf(IComponent):
def execute(self):
return "{} running".format(self.name)
# Concrete implementation of the composite class
class Composite(IComponent):
# This is the composite class (tree is composed of leafs)
def __init__(self, name="Root"):
super(Composite, self).__init__(name)
self.components = []
def add(self, c):
self.components.append(c)
return self
def remove(self, c):
self.components.remove(c)
return self
def get_children(self):
return self.components
def execute(self):
# Delegate work to all the children
for child in self.components:
print("{} / {}".format(self.name, child.execute()))
return "{} running".format(self.name)
if __name__ == "__main__":
# Define composite foo
product = Composite()
product.add(Leaf("Leaf 1"))
product.add(Leaf("Leaf 2"))
print(product.execute())
print()
# Nested composite foo
product.add(
Composite("Subsystem A")
.add(Leaf("Leaf 1"))
.add(Leaf("Leaf 2"))
)
print(product.execute())
Conditional Inheritance
# Conditional inheritance using the __new__ method
# ------------------------------------------------------------------------------
# The __new__ method can decide which subclass to instantiate. By inspecting
# runtime conditions it returns objects of different types from a single
# factory class. This approach allows conditional inheritance without altering
# the class hierarchy.
class WindowsCalculator(object):
""" Windows calculator class operations """
@staticmethod
def do():
print("Do in Windows Calculator")
class LinuxCalculator(object):
""" Linux calculator class operations """
@staticmethod
def do():
print("Do in Linux Calculator")
class Calculator(object):
def __new__(cls, os="windows"):
# Windows base class
if os == "windows":
base_class = (WindowsCalculator, )
# Linux base class
elif os == "linux":
base_class = (LinuxCalculator, )
# Invalid operating system
else:
raise ValueError("Invalid operating system")
# Create a new class with the given name and bases
cls = type("Calculator", base_class, {})
# Return an instance of the new class
return cls()
# Create a new instance of the Calculator class
calc = Calculator("windows")
# Check if the Calculator class is a subclass of the WindowsCalculator class
test = issubclass(type(calc), WindowsCalculator)
print("Is subclass of WindowsCalculator? [{}]".format(test))
# Call the do method
calc.do()
Decorator
# Example: Decorator Pattern
from abc import ABC, abstractmethod
class NotifierAbc(ABC):
@abstractmethod
def notify(self):
pass
class Notifier(NotifierAbc):
def notify(self):
print("Print on screen ...")
class NotifierDecorator(NotifierAbc):
def __init__(self, component):
self._component = component
@abstractmethod
def notify(self):
pass
class EmailNotifier(NotifierDecorator):
@staticmethod
def send_email():
print("Sending email ...")
def notify(self):
self.send_email()
self._component.notify()
class SMSNotifier(NotifierDecorator):
@staticmethod
def send_sms():
print("Sending SMS ...")
def notify(self):
self.send_sms()
self._component.notify()
def main():
notifier = Notifier()
notifier_with_email = EmailNotifier(notifier)
notifier_with_email_and_sms = SMSNotifier(notifier_with_email)
notifier_with_email_and_sms.notify()
if __name__ == "__main__":
main()
Facade
# Example: Facade Pattern
class PumpSystem(object):
@staticmethod
def prepare():
print("SubsystemA prepare ...")
@staticmethod
def run():
print("SubsystemA run ...")
class VentilationSystem(object):
@staticmethod
def prepare():
print("SubsystemB prepare ...")
@staticmethod
def run():
print("SubsystemB run ...")
class ComplexSystemFacade(object):
def __init__(self):
self._subsystemA = PumpSystem()
self._subsystemB = VentilationSystem()
def run(self):
self._subsystemA.prepare()
self._subsystemB.prepare()
self._subsystemA.run()
self._subsystemB.run()
def main():
system = ComplexSystemFacade()
system.run()
if __name__ == "__main__":
main()
Factory Class Method
# Example: Factory Method as a class method
class Transport(object):
def __init__(self):
pass
def deliver(self):
pass
class Truck(Transport):
def deliver(self):
print("Delivering by land in a box")
class Ship(Transport):
def deliver(self):
print("Delivering by sea in a container")
class Logistic(object):
def __init__(self, transport):
self.transport = transport
def deliver(self):
self.transport.deliver()
@classmethod
def from_json(cls, json):
# Factory method
# Get transport type
token = json.get("transport_type")
# Convert to lowercase and remove whitespace
transport_type = token.lower().strip(" \t\n\r")
# Factory logic
if transport_type == "truck":
return Truck()
elif transport_type == "ship":
return Ship()
else:
raise ValueError("Invalid transport type")
class App(object):
@staticmethod
def run():
# Select transport
json = {"transport_type": "truck"}
transport = Logistic.from_json(json)
# Deliver
logistic = Logistic(transport)
logistic.deliver()
if __name__ == "__main__":
app = App()
app.run()
Factory Static Method
# Example: Factory Method as a static Method
class Transport(object):
def __init__(self):
pass
def deliver(self):
pass
class Truck(Transport):
def deliver(self):
print("Delivering by land in a box")
class Ship(Transport):
def deliver(self):
print("Delivering by sea in a container")
class Logistic(object):
def __init__(self, transport):
self.transport = transport
def deliver(self):
self.transport.deliver()
class App(object):
@staticmethod
def select_transport():
# Factory method
# Get transport type
token = input("Select transport type: ")
# Convert to lowercase and remove whitespace
transport_type = token.lower().strip(" \t\n\r")
# Factory logic
if transport_type == "truck":
return Truck()
elif transport_type == "ship":
return Ship()
else:
raise ValueError("Invalid transport type")
def run(self):
# Select transport
transport = self.select_transport()
# Deliver
logistic = Logistic(transport)
logistic.deliver()
if __name__ == "__main__":
app = App()
app.run()
Flyweight
# Exercise: Flyweight Pattern
# Shared state (flyweight object)
class CoffeeFlavor(object):
def __init__(self, flavor):
self._flavor = flavor
def get_flavor(self):
return self._flavor
# Unique state (context object)
class CoffeeOrder(object):
def __init__(self, table_number, flavor):
self._table_number = table_number
self._flavor = flavor
def serve(self):
print(f"Serving coffee to table {self._table_number} with flavor {self._flavor.get_flavor()}")
# Flyweight factory (manages the flyweight and context objects)
class CoffeeShop(object):
def __init__(self):
# Cache for the orders and flavors
self._orders = {}
self._flavors = {}
def take_order(self, table_number, flavor_name):
# Check if the flavor instance is already cached
flavor = self._flavors.get(flavor_name)
# If not, create a new flavor instance and cache it
if not flavor:
flavor = CoffeeFlavor(flavor_name)
self._flavors[flavor_name] = flavor
# Create a new order with the shared flavor and unique table number
order = CoffeeOrder(table_number, flavor)
self._orders[table_number] = order
def serve_orders(self):
for table_number, order in self._orders.items():
order.serve()
# Client code
if __name__ == "__main__":
# Create the coffee shop
coffee_shop = CoffeeShop()
# Take orders from the customers
coffee_shop.take_order(1, "Cappuccino")
coffee_shop.take_order(2, "Espresso")
coffee_shop.take_order(3, "Cappuccino")
coffee_shop.take_order(4, "Espresso")
# Serve the orders
coffee_shop.serve_orders()
Interpreter
# Example: Interpreter Pattern
# Abstract Expression
class Expression(object):
def interpret(self, context):
raise NotImplementedError
# Terminal Expression
class Number(Expression):
def __init__(self, value):
self.value = value
def interpret(self, context):
return self.value
# Non-terminal Expression
class Add(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self, context):
return self.left.interpret(context) + self.right.interpret(context)
# Context
class Context(object):
def __init__(self):
self.variables = {}
def set(self, variable, value):
self.variables[variable] = value
def get(self, variable):
return self.variables.get(variable, 0)
# Client code
if __name__ == "__main__":
context = Context()
context.set("x", 10)
context.set("y", 5)
expression = Add(
Number(context.get("x")),
Number(context.get("y"))
)
result = expression.interpret(context)
print("Result: {}".format(result))
Iterator
# Example: Iterator Pattern
# Iterator interface
class Iterator(object):
def has_next(self):
pass
def next(self):
pass
# TreeNode represents a node in a tree
class TreeNode(object):
def __init__(self, data):
self.data = data
self.children = []
def add_child(self, child):
self.children.append(child)
# Concrete Iterator for tree traversal
class TreeIterator(Iterator):
def __init__(self, root):
self.stack = [root]
def has_next(self):
return len(self.stack) > 0
def next(self):
if not self.has_next():
raise StopIteration()
node = self.stack.pop()
for child in reversed(node.children):
self.stack.append(child)
return node.data
# Client code
if __name__ == "__main__":
# Create a sample tree structure
root = TreeNode("Root")
child1 = TreeNode("Child 1")
child2 = TreeNode("Child 2")
child3 = TreeNode("Child 3")
# Add nodes to the tree
root.add_child(child1)
root.add_child(child2)
child2.add_child(child3)
# Create a tree iterator
iterator = TreeIterator(root)
# Traverse and print tree nodes
while iterator.has_next():
node = iterator.next()
print(node)
# Example: Mediator Pattern
# Mediator interface
class Mediator(object):
def forward(self, message, component):
raise NotImplementedError
# Concrete Mediator
class Dialog(Mediator):
def __init__(self):
self.components = []
def set_component(self, component):
self.components.append(component)
def forward(self, message, sender):
for component in self.components:
if sender is not component:
component.receive(message)
# Component interface
class Component(object):
def __init__(self, mediator):
self.mediator = mediator
def send(self, message):
raise NotImplementedError
def receive(self, message):
raise NotImplementedError
# Concrete Component
class Button(Component):
def send(self, message):
print(f"Button sends: {message}")
self.mediator.forward(message, self)
def receive(self, message):
print(f"Button receives: {message}")
# Concrete Component
class Textbox(Component):
def send(self, message):
print(f"Textbox sends: {message}")
self.mediator.forward(message, self)
def receive(self, message):
print(f"Textbox receives: {message}")
# Concrete Component
class Checkbox(Component):
def send(self, message):
print(f"Checkbox sends: {message}")
self.mediator.forward(message, self)
def receive(self, message):
print(f"Checkbox receives: {message}")
# Client code
if __name__ == "__main__":
# Create the mediator
mediator = Dialog()
# Add components to the mediator
button = Button(mediator)
mediator.set_component(button)
# Add more components to the mediator
textbox = Textbox(mediator)
mediator.set_component(textbox)
# Add more components to the mediator
checkbox = Checkbox(mediator)
mediator.set_component(checkbox)
# Send messages
button.send("Hello from Button!")
Memento
# Example: Memento Pattern
# Originator class
class TextEditor:
def __init__(self):
self.content = ""
def write(self, text):
self.content += text
def show_content(self):
print(f"Editor Content: {self.content}")
def create_memento(self):
return TextEditorMemento(self.content)
def restore_from_memento(self, memento):
self.content = memento.get_state()
# Memento class
class TextEditorMemento:
def __init__(self, content):
self.content = content
def get_state(self):
return self.content
# Caretaker class
class History:
def __init__(self):
self.states = []
def push(self, memento):
self.states.append(memento)
def pop(self):
if self.states:
return self.states.pop()
return None
# Client code
if __name__ == "__main__":
text_editor = TextEditor()
history = History()
text_editor.write("Hello, ")
history.push(text_editor.create_memento())
text_editor.write("world!")
history.push(text_editor.create_memento())
text_editor.show_content()
text_editor.write(" Hello, hello!")
text_editor.show_content()
# Undo the last action
last_state = history.pop()
if last_state:
text_editor.restore_from_memento(last_state)
text_editor.show_content()
Mixin Class
# Mixin class
# ------------------------------------------------------------------------------
# A mixin provides extra methods that can be shared across multiple unrelated
# classes. It relies on cooperative multiple inheritance to join its behavior
# with that of the main class hierarchy.
class RemoteMixin(object):
def __init__(self, brand=None, volume=0, *args, **kwargs):
# This syntax is required in order to guarantee that the MRO is not broken
super(RemoteMixin, self).__init__(*args, **kwargs)
# Mixin specific attributes
self.brand = brand
self.volume = volume
def volume_up(self):
self.volume += 1
def volume_down(self):
self.volume -= 1
def status(self):
print("Brand: {}".format(self.brand))
print("Volume: {}".format(self.volume))
class JvcRemote(RemoteMixin, object):
""" Mixins should be always inherited first """
def __init__(self):
super(JvcRemote, self).__init__(brand="JVC", volume=10)
def status(self):
super(JvcRemote, self).status()
@staticmethod
def learn():
print("Learn button")
class SonyRemote(RemoteMixin, object):
""" Mixins should be always inherited first """
def __init__(self):
super(SonyRemote, self).__init__(brand="Sony", volume=5)
@staticmethod
def home():
print("Home button")
remote = JvcRemote()
actions = ["volume_up", "status", "volume_down", "status", "learn", "status"]
for action in actions:
print("Action: {}".format(action))
func = getattr(remote, action)
func()
print("\n")
remote = SonyRemote()
actions = ["volume_up", "status", "volume_down", "status", "home", "status"]
for action in actions:
print("Action: {}".format(action))
func = getattr(remote, action)
func()
Observer
# Example: Observer Pattern
# Subject Interface (produces data)
class Publisher(object):
def attach(self, observer):
pass
def detach(self, observer):
pass
def notify(self):
pass
# Concrete Subject
class WeatherData(Publisher):
def __init__(self):
self.subscribers = []
self.temperature = 0.0
self.humidity = 0.0
self.pressure = 0.0
def attach(self, observer):
self.subscribers.append(observer)
def detach(self, observer):
self.subscribers.remove(observer)
def notify(self):
for subscriber in self.subscribers:
subscriber.update(self.temperature, self.humidity, self.pressure)
def set(self, temperature, humidity, pressure):
self.temperature = temperature
self.humidity = humidity
self.pressure = pressure
self.notify()
# Observer interface (consumes data)
class Subscriber(object):
def update(self, temperature, humidity, pressure):
pass
# Concrete Observer
class DisplayDevice(Subscriber):
def __init__(self, name):
self.name = name
def update(self, temperature, humidity, pressure):
print(
f"{self.name} Display - Temperature: {temperature}°C, Humidity: {humidity}%, Pressure: {pressure} hPa")
# Client code
if __name__ == "__main__":
# Create a weather station
weather_station = WeatherData()
# Attach a display device to the weather station
display1 = DisplayDevice("Display 1")
weather_station.attach(display1)
# Attach another display device to the weather station
display2 = DisplayDevice("Display 2")
weather_station.attach(display2)
# Simulate changes in weather data
weather_station.set(25.0, 60.0, 1013.0)
weather_station.set(26.5, 55.0, 1010.5)
Prototype
# Example: Prototype Pattern
from abc import ABC, abstractmethod
# Prototype interface
class Prototype(ABC):
@abstractmethod
def clone(self):
pass
# Concrete Prototype
class Pizza(Prototype):
def __init__(self, crust, cheese, toppings):
self.crust = crust
self.cheese = cheese
self.toppings = toppings
def __str__(self):
return "Crust: {0}\nCheese: {1}\nToppings: {2}".format(
self.crust,
self.cheese,
", ".join(self.toppings)
)
def clone(self):
return Pizza(self.crust, self.cheese, self.toppings)
if __name__ == "__main__":
pizza = Pizza("thin", "mozzarella", ["pepperoni", "mushrooms"])
pizza_clone = pizza.clone()
print(pizza_clone)
print(pizza == pizza_clone)
print(pizza is pizza_clone)
Proxy
# Example: Proxy Pattern
class Server(object):
def request(self):
raise NotImplementedError
class RealServer(Server):
def request(self):
print("RealServer: Handling request.")
class ProxyServer(Server):
def __init__(self, server: Server = None):
self._server = server
def request(self):
if self.check_access():
self._server.request()
self.log_access()
@staticmethod
def check_access():
print("ProxyServer: Checking access prior to firing a real request.")
return True
@staticmethod
def log_access():
print("ProxyServer: Logging the time of request.", end="")
class Client(object):
def __init__(self, server: Server):
self._server = server
def execute(self):
self._server.request()
if __name__ == "__main__":
print("Client: Executing the client code with a real server")
real_server = RealServer()
client = Client(server=real_server)
client.execute()
print("")
print("Client: Executing the same client code with a proxy server")
client = Client(server=ProxyServer(real_server))
client.execute()
Singleton
# Example: Singleton Pattern with __new__
class Singleton(object):
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
# Singleton using the __new__ method
# --------------------------------------------------------------------------------
# By overriding __new__, this class ensures that only one instance ever exists.
# The method stores the created object and returns it on subsequent calls.
# Such control over object creation implements the singleton pattern.
class Singleton(object):
__instance = None
def __new__(cls):
if cls.__instance is None:
print("Creating singleton...")
cls.__instance = object.__new__(cls)
else:
print("Singleton already exists...")
return cls.__instance
s1 = Singleton()
s2 = Singleton()
print(s1 == s2)
print(s1 is s2)
State
# State interface
class State(object):
def connect(self):
raise NotImplementedError
def disconnect(self):
raise NotImplementedError
def send_data(self, data):
raise NotImplementedError
# Concrete State: Disconnected
class DisconnectedState(State):
def connect(self):
# Action
print("Connecting to the server...")
# Transition to the Connected state
return ConnectedState()
def disconnect(self):
print("Already disconnected.")
return self
def send_data(self, data):
print("Cannot send data while disconnected.")
return self
# Concrete State: Connected
class ConnectedState(State):
def connect(self):
# Action
print("Already connected.")
# Transition
return self
def disconnect(self):
# Action
print("Disconnecting from the server...")
# Transition
return DisconnectedState()
def send_data(self, data):
# Action
print(f"Sending data to the server: {data}")
# Transition
return self
# Context class
class Client(object):
def __init__(self):
# Initial state
self.state = DisconnectedState()
def connect(self):
self.state = self.state.connect()
def disconnect(self):
self.state = self.state.disconnect()
def send_data(self, data):
self.state.send_data(data)
# Client code
if __name__ == "__main__":
client = Client()
client.send_data("Hello, server!") # Try sending data while disconnected
client.connect()
client.send_data("Hello, server!") # Send data after connecting
client.connect() # Try connecting again
client.disconnect()
client.send_data("Goodbye, server!") # Send data after disconnecting
Strategy
# Example: Strategy Pattern
# Strategy interface
class TextFormatter(object):
def format_text(self, text):
raise NotImplementedError
# Concrete Strategy: Uppercase formatting
class UppercaseFormatter(TextFormatter):
def format_text(self, text):
return text.upper()
# Concrete Strategy: Lowercase formatting
class LowercaseFormatter(TextFormatter):
def format_text(self, text):
return text.lower()
# Concrete Strategy: Title case formatting
class TitleCaseFormatter(TextFormatter):
def format_text(self, text):
return text.title()
# Context class
class TextEditor(object):
def __init__(self, formatter):
self.formatter = formatter
def set_formatter(self, formatter):
self.formatter = formatter
def format_text(self, text):
return self.formatter.format_text(text)
# Client code
if __name__ == "__main__":
text = "This is a simple example of the Strategy Pattern."
# Create text editor with the default uppercase formatting strategy
editor = TextEditor(UppercaseFormatter())
result = editor.format_text(text)
print("Uppercase Formatting:")
print(result)
# Change the formatting strategy to lowercase
editor.set_formatter(LowercaseFormatter())
result = editor.format_text(text)
print("\nLowercase Formatting:")
print(result)
# Change the formatting strategy to title case
editor.set_formatter(TitleCaseFormatter())
result = editor.format_text(text)
print("\nTitle Case Formatting:")
print(result)
Template Method
# Example: Template Method Pattern
# Abstract with template methods
class Notification(object):
# Template method
def send_notification(self, message):
self.authenticate()
self.format_message(message)
self.send_message()
# Template method
def authenticate(self):
print("Authentication successful")
# Template method
def format_message(self, message):
print(f"Formatting message: {message}")
# Abstract method
def send_message(self):
raise NotImplementedError
# Concrete Notification subclass for email
class EmailNotification(Notification):
def send_message(self):
print("Sending email...")
# Concrete Notification subclass for SMS
class SMSNotification(Notification):
def send_message(self):
print("Sending SMS...")
# Client code
if __name__ == "__main__":
email_notification = EmailNotification()
sms_notification = SMSNotification()
text = "This is a notification message."
print("Email Notification:")
email_notification.send_notification(text)
print("\nSMS Notification:")
sms_notification.send_notification(text)
Visitor
# Example: Visitor Pattern
class ExportVisitor(object):
def visit(self, element):
pass
class XMLExportVisitor(ExportVisitor):
def visit(self, element):
print('XML exporter visiting element of type {0}'.format(type(element).__name__))
class Node(object):
def accept(self, visitor):
pass
class City(Node):
def accept(self, visitor):
visitor.visit(self)
class Industry(Node):
def accept(self, visitor):
visitor.visit(self)
class NavigationMap(object):
def __init__(self):
self.nodes = []
def add(self, element):
self.nodes.append(element)
def accept(self, visitor):
for element in self.nodes:
element.accept(visitor)
if __name__ == "__main__":
exporter = XMLExportVisitor()
object_structure = NavigationMap()
object_structure.add(City())
object_structure.add(Industry())
object_structure.accept(exporter)