I wanted to share a post with some notes from the Jungwoo Ryoo Python Design Patterns course. Dr. Jungwoo Ryoo educates people in the field of Software Security/cybersecurity, Computer networking, Data Science and others.
CONTENTS:
1. Creational Patterns
Factory Method
Abstract Factory Method
Singleton Method
Builder Method
Prototype Method
2. Structural Patterns
Decorator Method
Proxy Method
Adapter Method
Composite Method
Bridge Method
3. Behavioral Patterns
Observer Method
Visitor Method
Iterator Method
Strategy Method
Chain of Responsability Method
Command Method
Mediator Method
Memento Method
State Method
Template Method
1. Creational Design Patterns
Creational patterns provides essential information regarding the Class instantiation or the object instantiation. Class Creational Pattern and the Object Creational pattern is the major categorization of the Creational Design Patterns. While class-creation patterns use inheritance effectively in the instantiation process, object-creation patterns use delegation effectively to get the job done.
Factory class Method
To create an object with a instanciated factory on the run with out knowing how, why or what parameters are you going to pass
Allows an interface or a class to create an object, but lets subclasses decide which class or object to instantiate. Using the Factory method, we have the best ways to create an object. Here, objects are created without exposing the logic to the client, and for creating the new type of object, the client uses the same common interface.
class SocketH:
"""Class example object connection HTTP"""
def __init__(self, name):
self.name = name
def action(self):
return f"Connection: {self.name}"
class SocketF:
"""Class example object connection FTP"""
def __init__(self, name):
self.name = name
def action(self):
return f"Connection: {self.name}"
def get_connection(connection="http"):
"""The factory method"""
connections = {'http': SocketH("http_connection"),
'ftp': SocketF('ftp_connection')}
return connections[connection]
# Running factory function
http = get_connection('http')
print(f"object -> {http.action()}")
ftp = get_connection('FTP')
print(f"Object -> {ftp.action()}")
object -> Connection: http_connection
Object -> Connection: ftp_connection
Abstract Factory Method
Allows you to produce the families of related objects without specifying their concrete classes. Using the abstract factory method, we have the easiest ways to produce a similar type of many objects.
It provides a way to encapsulate a group of individual factories. When the user expectation yields multiple, related objects at a given time but don't need to know which family it is until runtime.
class SocketH:
"""One of the objects to be returned"""
def action(self):
return f"Connection: http"
def __str__(self):
return "Socket_h"
class SocketHFactory:
"""Concrete factory"""
def get_connection(self):
"""Returns a Connection object"""
return SocketH()
def get_request(self):
"""Returns a Get Request object"""
return "get request"
class Connections:
"""Connections class houses our abstract factory"""
def __init__(self, connection_factory = None):
"""Connections class is our abstract factory"""
self._connection_factory = connection_factory
def show_connection(self):
"""Utility method to display details of the objects returned
by the Connection factory"""
connection = self._connection_factory.get_connection()
request = self._connection_factory.get_request()
print(f"Connection: {connection}")
print(f"Action {connection.action()}")
print(f"Request: {request}")
# Create a Concrete Factory
factory_a = SocketHFactory()
factory_b = SocketHFactory()
# Create a connections, our Abstract Factory
connect = Connections(factory_a)
# Invoke the utility method to show the details
connect.show_connection()
Connection: Socket_h
Action Connection: http
Request: get request
Singleton Method
Allows to keep information in a single object there is no need to extract the information every time. When you want to allow only one object to be instantiated from a class.
Singleton Design Pattern can be understood by a very simple example of Database connectivity. When each object creates a unique Database Connection to the Database, it will highly affect the cost and expenses of the project. So, it is always better to make a single connection rather than making extra irrelevant connections which can be easily done by Singleton Design Pattern.
class Borg:
"""Borg class attributes global"""
_shared_state = {} # Attribute dictionary
def __init__(self):
self.__dict__ = self._shared_state
class Singleton(Borg): # Inherits from the Borg class
"""This class now shares all its attributes among its various instances"""
# This essentially makes the singleton objects an object-oriented
def __init__(self, **kwargs):
# Update the attribute dictionary by insrting a new key-value pair
self._shared_state.update(kwargs)
def __str__(self):
return str(self._shared_state)
# Create a singleton object and add our fiest acronym
singleton = Singleton(HTTP = "Hyper Text Transfer Protocol")
# Print the updated object
print(f"First print: {singleton}")
# Create another singleton object by adding another acronym
singleton = Singleton(SNMP = "Simple Network Management Protocol")
# Print the updated object
print(f"Second print: {singleton}")
First print: {'HTTP': 'Hyper Text Transfer Protocol'}
Second print: {'HTTP': 'Hyper Text Transfer Protocol', 'SNMP': 'Simple Network Management Protocol'}
Builder Method
Builder Method is a Creation Design Pattern which aims to “Separate the construction of a complex object from its representation so that the same construction process can create different representations.” It allows you to construct complex objects step by step. Here using the same construction code, we can produce different types and representations of the object easily.
To avoid telescoping constructor anti-pattern.
- Director: builds the project
- Abstract Builder: interfaces
- Concrete Builder: implements the interfaces
- Product: object being build
class Director:
"""Director class"""
def __init__(self, builder):
self._builder = builder
def construct_obj(self):
self._builder.create_new_obj()
self._builder.add_user()
self._builder.add_connection()
self._builder.add_name()
def get_obj(self):
return self._builder.obj
class Builder:
"""Abstract Builder"""
def __init__(self):
self.obj = None
def create_new_obj(self):
self.obj = Obj()
class ConcreteBuilder(Builder):
"""Concrete Builder --> Provides parts and tools to work on the parts"""
def add_user(self):
self.obj.user = "skwh"
def add_connection(self):
self.obj.connection = "HTTP"
def add_name(self):
self.obj.name = "Skyler White"
class Obj:
"""Product"""
def __init__(self):
self.user = None
self.connection = None
self.name = None
def __str__(self):
return f"{self.user} | {self.connection} | {self.name}"
builder = ConcreteBuilder()
director = Director(builder)
director.construct_obj()
obj = director.get_obj()
print(obj)
skwh | HTTP | Skyler White
Prototype Method
Prototype Method is a Creational Design Pattern which aims to reduce the number of classes used for an application. It allows you to copy existing objects independent of the concrete implementation of their classes. Generally, here the object is created by copying a prototypical instance during run-time.
It is highly recommended to use Prototype Method when the object creation is an expensive task in terms of time and usage of resources and already there exists a similar object. This method provides a way to copy the original object and then modify it according to our needs.
import copy
class Prototype:
def __init__(self):
self._objects = {}
def register_object(self, name, obj):
"""Register an object"""
self._objects[name] = obj
def unregister_object(self, name):
"""Unregister an object"""
del self._objects[name]
def clone(self, name, **attr):
"""Clone a registered object"""
obj = copy.deepcopy(self._objects[name])
obj.__dict__.update(attr)
return obj
class Api:
def __init__(self):
self.name = "api_name"
self.ip = "192.168.23.85"
self.option = True
def __str__(self):
return f"api -> {self.name} | {self.ip} | {self.option}"
api = Api()
prototype = Prototype()
prototype.register_object("api_name", api)
api_cloned = prototype.clone("api_name", ip = "232.0.0.1")
print(api_cloned)
api -> api_name | 232.0.0.1 | True
2. Structural Design Patterns
Structural design patterns are about organizing different classes and objects to form larger structures and provide new functionality while keeping these structures flexible and efficient. Mostly they use Inheritance to compose all the interfaces. It also identifies the relationships which led to the simplification of the structure.
Decorator Method
Decorator Method is a Structural Design Pattern which allows you to dynamically attach new behaviors to objects without changing their implementation by placing these objects inside the wrapper objects that contains the behaviors.
It is much easier to implement Decorator Method in Python because of its built-in feature. It is not equivalent to the Inheritance because the new feature is added only to that particular object, not to the entire subclass.
from functools import wraps
def make_blink(function):
"""Defines the decorator"""
# This makes the decoratos transparent in terms of its name and docstring
@wraps(function)
def decorator():
# Grab the return value of the function being decorated
ret = function()
# Add new funcionality to the function being decorated
return f"<blink> {ret} </blink>"
return decorator
# Apply the decorator here!
@make_blink
def hello_world():
"""Original function!"""
return "Hello, World!"
# Check the result of decorating
print(hello_world())
# Check if the function name is still the same name of the function being decorated.
print(hello_world.__name__)
# Check if the docstring is still the same as that of the function being decorated.
print(hello_world.__doc__)
<blink> Hello, World! </blink>
hello_world
Original function!
Proxy Method
The Proxy method is Structural design pattern that allows you to provide the replacement for an another object. Here, we use different classes to represent the functionalities of another class. The most important part is that here we create an object having original object functionality to provide to the outer world.
The meaning of word Proxy is “in place of” or “on behalf of” that directly explains the Proxy Method. Proxies are also called surrogates, handles, and wrappers. They are closely related in structure, but not purpose, to Adapters and Decorators.
import time
class Producer:
"""Define the 'resource-intensive' object to instantiate"""
def produce(self):
print("Producer is working hard!")
def meet(self):
print("Producer has time to meet you now!")
class Proxy:
"""Define the relatively 'less resource-intensive proxy' to instatiate as a middleman"""
def __init__(self):
self.occupied = False
self.producer = None # Instance of the producer class
def produce(self):
"""Check if Producer is available"""
print("Artist checking if Producer is available...")
if self.occupied == False:
# If the Producer is available, create a producer object
self.producer = Producer()
time.sleep(2)
# Make the producer meet the guest
self.producer.meet()
else:
# Otherwise, don't instantiate a producer
time.sleep(2)
print("Producer is busy!")
# Instantiate a Proxy
proxy = Proxy()
# Make the Proxy: Artist produce until Producer is available
proxy.produce()
# Change the state to 'Occupied'
proxy.occupied = True
# Make the Producer produce
proxy.produce()
Artist checking if Producer is available...
Producer has time to meet you now!
Artist checking if Producer is available...
Producer is busy!
Adapter Method
Adapter method is a Structural Design Pattern which helps us in making the incompatible objects adaptable to each other. The Adapter method is one of the easiest methods to understand because we have a lot of real-life examples that show the analogy with it. The main purpose of this method is to create a bridge between two incompatible interfaces. This method provides a different interface for a class. We can more easily understand the concept by thinking about the Cable Adapter that allows us to charge a phone somewhere that has outlets in different shapes.
Using this idea, we can integrate the classes that couldn’t be integrated due to interface incompatibility.
class Spanish:
"""Korean speaker"""
def __init__(self):
self.name = "Spanish"
def speak_spanish(self):
return "Hola!"
class British:
"""British speaker"""
def __init__(self):
self.name = "British"
# Make the difference method name here!
def speak_english(self):
return "Hello!"
class Adapter:
"""This changes the generic method name to individualized method names"""
def __init__(self, object, **adapter_method):
"""Change the name of the method"""
self._object = object
# add a new dictionary item that establishes the mapping between the generic method name: speak() and the concrete method
# for example, speak() will be translated to speak_korean() if the mapping says so
self.__dict__.update(adapter_method)
def __getattr__(self, attr):
"""Simply return the rest of the attributes!"""
return getattr(self._object, attr)
# List to store speaker objects
objects = []
# Create Spanish object
spanish = Spanish()
# Create British object
british = British()
# Append the objects to the objects list
objects.append(Adapter(spanish, speak = spanish.speak_spanish))
objects.append(Adapter(british, speak=british.speak_english))
for obj in objects:
print(f"{obj.name} says {obj.speak()}\n")
Spanish says Hola!
British says Hello!
Composite Method
Composite Method is a Structural Design Pattern which describes a group of objects that is treated the same way as a single instance of the same type of the objects. The purpose of the Composite Method is to Compose objects into Tree type structures to represent the whole-partial hierarchies.
Composite Method is a Structural Design Pattern which describes a group of objects that is treated the same way as a single instance of the same type of the objects. The purpose of the Composite Method is to Compose objects into Tree type structures to represent the whole-partial hierarchies.
class Component(object):
"""Abstract class"""
def __init__(self, *args, **kwargs):
pass
def component_function(self):
pass
class Child(Component): # Inherits from the abstract class Component
"""Concrete class"""
def __init__(self, *args, **kwargs):
Component.__init__(self, *args, **kwargs)
# This is where we store the name of your child item!
self.name = args[0]
def component_function(self, tabs):
"""Print the name of your child item here"""
tab_spaces = "\t"*tabs
print(f"{tab_spaces}{self.name}")
class Composite(Component): # Inherits from the abstract class Component
"""Concrete class and mantains the tree recursive structure"""
def __init__(self, *args, **kwargs):
Component.__init__(self, *args, **kwargs)
# This is where we store the name of composite object!
self.name = args[0]
# This is where we keep ur child items
self.children = []
def append_child(self, child):
"""Method to add a new child item"""
self.children.append(child)
def remove_child(self, child):
"""Method to remove a new child item"""
self.children.remove(child)
def component_function(self, tabs = 0):
# Print the name of the composite object
tab_spaces = "\t"*tabs
print(f"{tab_spaces}{self.name}")
tabs += 1
# Iterate through the child objects and invoke theircomponent function printing their names
for i in self.children:
i.component_function(tabs = tabs)
# Build a composite submenu 1
sub1 = Composite("submenu1")
# Create a new child sub_menu 11
sub11 = Child("sub_menu 11")
# Create a new child sub_menu 12
sub12 = Child("sub_menu 12")
# Add the sub_menu 11 to submenu 1
sub1.append_child(sub11)
# Add the sub_menu 12 to submenu 1
sub1.append_child(sub12)
# Build a top-level composite menu
top = Composite("top_menu")
# Build a submenu 2 that is not a composite
sub2 = Composite("submenu2")
sub2.append_child(sub11)
# Add the composite submenu 1 to the top-level composite menu
top.append_child(sub1)
# Add the plain submenu 2 to the top-level composite menu
top.append_child(sub2)
# Let's test if our composite pattern works!
top.component_function()
top_menu
submenu1
sub_menu 11
sub_menu 12
submenu2
sub_menu 11
Bridge Method
Bridge method is a Structural Design Pattern which allows us to separate the Implementation Specific Abstractions and Implementation Independent Abstractions from each other and can be developed considering as the single entities.
Bridge Method is always considered as one of the best methods to organize the class hierarchy.
Elements of Bridge Design Pattern:
- Abstraction: It is the core of the Bridge Design Pattern and it provides the reference to the implementer.
- Refined Abstraction: It extends the abstraction to the new level where it takes the finer details one level above and hides the finer element from the implementors.
- Implementer: It defines the interface for implementation classes. This interface does not need to correspond directly to the abstraction interface and can be very different.
- Concrete Implementation: Through the concrete implementation, it implements the above implementer.
class DrawingAPIOne(object):
"""Implementation-specific abstraction: concrete class one"""
def draw_circle(self, x, y, radius):
print(f"API 1 drawing a circle at ({x}, {y} with radius {radius}!)")
class DrawingAPITwo(object):
"""Implementation-specific abstraction: concrete class two"""
def draw_circle(self, x, y, radius):
print(f"API 2 drawing a circle at ({x}, {y} with radius {radius}!)")
class Circle(object):
"""Implementation-independent abstraction: for example, there could be a rectangle class"""
def __init__(self, x, y, radius, drawing_api):
"""Initialize the necessary attributes"""
self._x = x
self._y = y
self._radius = radius
self._drawing_api = drawing_api
def draw(self):
"""Implementation-specific abstraction taken care of by another class: DrawingAPI"""
self._drawing_api.draw_circle(self._x, self._y, self._radius)
def scale(self, percent):
"""Implementation independent"""
self._radius *= radius
# Build the first circle object using API One
circle1 = Circle(1, 2, 3, DrawingAPIOne())
# Build the
circle1.draw()
# Build the second circle object using API One
circle2 = Circle(2, 3, 4, DrawingAPITwo())
circle2.draw()
API 1 drawing a circle at (1, 2 with radius 3!)
API 2 drawing a circle at (2, 3 with radius 4!)
3. Behavioral Design Patterns
Behavioral patterns are all about identifying the common communication patterns between objects and realize these patterns. These patterns are concerned with algorithms and the assignment of responsibilities between objects.
Observer Method
The observer method is a Behavioral design Pattern which allows you to define or create a subscription mechanism to send the notification to the multiple objects about any new event that happens to the object that they are observing. The subject is basically observed by multiple objects. The subject needs to be monitored and whenever there is a change in the subject, the observers are being notified about the change. This pattern defines one to Many dependencies between objects so that one object changes state, all of its dependents are notified and updated automatically.
One-two many relationships between a subject and multiple observers.
Example: Core temperatures monitored by observers.
- Subject: abstract class (Attach Detach Notify)
- Concrete subjects
class Subject(object): # Represents what is being 'observed'
"""This is where references to all the observers
are being kept. Note that this is a one-to-
many relationship: there will be one subject
to be observed by multiple observers."""
def __init__(self):
self._observers = []
def attach(self, observer):
"""If the observer is not already in the observers list
append the observer to the list"""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, modifier=None):
"""Simply remove the observer"""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self, modifier = None):
"""For all the observers in the list. Don't notify the observer
who is actually updating the temperature of the core"""
for observer in self._observers:
if modifier != observer:
observer.update(self)
class Core(Subject): # Inherits from the Subject class
def __init__(self, name = ""):
Subject.__init__(self)
self._name = name # Set the name of the core
self._temp = 0 # Initiaize the temperature of the core
@property # Getter that gets the core temperature
def temp(self):
return self._temp
@temp.setter # Setter that sets the core temperature
def temp(self, temp):
"""Notify the observers whenever somebody changes the core temperature"""
self._temp = temp
self.notify()
class TempViewer:
"""A printer for the temperature"""
def update(self, subject):
"""Alert method that is invoked when the notify()
method in a concrete subject is invoked"""
print(f"Temperature Viewer: {subject._name} has temperature {subject._temp}")
# CODE
# Let's create our subjects
c1 = Core("Core 1")
c2 = Core("Core 2")
# Let's create our observers
v1 = TempViewer()
v2 = TempViewer()
# Let's attach our observers to the first core
c1.attach(v1)
c1.attach(v2)
# Let's change the temperature of our first core
c1.temp = 80
c1.temp = 90
Temperature Viewer: Core 1 has temperature 80
Temperature Viewer: Core 1 has temperature 80
Temperature Viewer: Core 1 has temperature 90
Temperature Viewer: Core 1 has temperature 90
Visitor Method
Visitor Method is a Behavioral Design Pattern which allows us to separate the algorithm from an object structure on which it operates. It helps us to add new features to an existing class hierarchy dynamically without changing it. All the behavioral patterns proved as the best methods to handle the communication between the objects. Similarly, it is used when we have to perform an operation on a group of similar kinds of objects.
class House(object):
"""The class being visited"""
def accept(self, visitor):
"""Interface to accept a visitor, triggers the visiting operation!"""
visitor.visit(self)
def work_on_hvac(self, hvac_specialist):
"""Reference to the hvac specialist object in the house object"""
print(f"{self} worked on by {hvac_specialist}")
def work_on_electricity(self, electrician):
"""Reference to the electrician object in the house object"""
print(f"{self} worked on by {electrician}")
def __str__(self):
"""Simply return the class name when the House object is printed"""
return self.__class__.__name__
class Visitor(object):
"""Abstract visitor"""
def __str__(self):
"""Simply return the class name when the visitor object is printed"""
return self.__class__.__name__
class HvacSpecialist(Visitor):
"""Concrete visitor: electrician
Inherits from the parent class, Visitor"""
def visit(self, house):
house.work_on_hvac(self)
class Electrician(Visitor):
"""Concrete visitor: electrician
Inherits from the parent class, Visitor"""
def visit(self, house):
house.work_on_electricity(self)
# CODE
# Create an HVAC especialist
hvac_specialist = HvacSpecialist()
# Create an electritian
electrician = Electrician()
# Create a House
house = House()
# Let the house accept the HVAC specialist and work on the house
# by invokation the visit() method
house.accept(hvac_specialist)
# Let the house accept the HVAC specialist and work on the house
# by invokation the visit() method
house.accept(electrician)
House worked on by HvacSpecialist
House worked on by Electrician
Iterator Method
Iterator method is a Behavioral Design Pattern that allows us to traverse the elements of the collections without taking the exposure of in-depth details of the elements. It provides a way to access the elements of complex data structure sequentially without repeating them.
According to GangOfFour, Iterator Pattern is used ” to access the elements of an aggregate object sequentially without exposing its underlying implementation”.
def count_to(count):
"""Our iterator implementation"""
# Our list
numbers_in_spanish = ['uno', 'dos', 'tres', 'cuatro', 'cinco',
'seis', 'siete', 'ocho', 'nueve', 'diez']
# Our build in iterator
# Creates a tuple such as (1, "uno")
iterator = zip(range(count), numbers_in_spanish)
# Iterate thourgh our iterable list
# Extract the Spanish numbers
# put them in a generator called number
for position, number in iterator:
# Returns a 'generator' containing numbers in Spanish
yield number
# CODE
# Lest's test the generator returned by our iterator
for i in count_to(8):
print(f"The number is: {i}.")
The number is: uno.
The number is: dos.
The number is: tres.
The number is: cuatro.
The number is: cinco.
The number is: seis.
The number is: siete.
The number is: ocho.
Strategy Method
The strategy method is Behavioral Design pattern that allows you to define the complete family of algorithms, encapsulates each one and putting each of them into separate classes and also allows to interchange there objects. It is implemented in Python by dynamically replacing the content of a method defined inside a class with the contents of functions defined outside of the class. It enables selecting the algorithm at run-time. This method is also called as Policy Method.
import types # Types module
class Strategy:
"""The Strategy Pattern class"""
def __init__(self, function = None):
self.name = "Default Strategy"
# If a reference to a function is provided, replace the execute() method with the given function.
if function:
self.execute = types.MethodType(function, self)
def execute(self):
"""The default method that prints the name of the strategy being used"""
print(f"{self.name} is used!")
# Replacement method 1
def strategy_one(self):
print(f"{self.name} is used to execute method 1")
def strategy_two(self):
print(f"{self.name} is used to execute method 2")
# CODE
# Let's create our default strategy
s0 = Strategy()
# Let's execute our default strategy
s0.execute()
# Let's create our first variation of our default strategy by providing a new behavior
s1 = Strategy(strategy_one)
# Let's set its name
s1.name = "Strategy_one"
# Let's execute the strategy
s1.execute()
# Let's create our first variation of our default strategy by providing a new behavior
s2 = Strategy(strategy_two)
# Let's set its name
s2.name = "Strategy_two"
# Let's execute the strategy
s2.execute()
Default Strategy is used!
Strategy_one is used to execute method 1
Strategy_two is used to execute method 2
Chain of Responsability Method
Chain of Responsibility method is Behavioral design pattern and it is the object-oriented version of if … elif … elif … else and make us capable to rearrange the condition-action blocks dynamically at the run-time. It allows us to pass the requests along the chain of handlers. The processing is simple, whenever any handler received the request it has two choices either to process it or pass it to the next handler in the chain.
This pattern aims to decouple the senders of a request from its receivers by allowing the request to move through chained receivers until it is handled.
class Handler:
"""Abstract Handler"""
def __init__(self, succesor):
"""Define who is the next handler"""
self._succesor = succesor
def handle(self, request):
handled = self._handle(request)
if not handled:
self._succesor.handle(request)
def _handle(self, request):
raise NotImplementedError("Must provide implementation in subclass!")
class ConcreteHandler1(Handler):
"""Concrete handler 1"""
def _handle(self, request):
if 0 < request <= 10: # Provide a condition for handling
print(f"Request {request} handled in handeler 1")
return True # Indicates the request have been handled
class DefaultHandler(Handler):
"""Default handler"""
def _handle(self, request):
"""If ther is no handler available. No condition checking since
this is a default handler"""
print(f"End of chain, no handler for {request}")
return True # Indicates that the request has been handled
class Client:
"""Using handlers"""
def __init__(self):
"""Create handlers and use them in a sequence you want"""
self.handler = ConcreteHandler1(DefaultHandler(None))
def delegate(self, requests):
"""Send your requestas one at a time for handlers to handle"""
for request in requests:
self.handler.handle(request)
# CODE
# Create a client
client = Client()
# Create requests
requests = [2, 5, 30]
# Send requests
client.delegate(requests)
Request 2 handled in handeler 1
Request 5 handled in handeler 1
End of chain, no handler for 30
Command Method
Command Method is Behavioral Design Pattern that encapsulates a request as an object, thereby allowing for the parameterization of clients with different requests and the queuing or logging of requests. Parameterizing other objects with different requests in our analogy means that the button used to turn on the lights can later be used to turn on stereo or maybe open the garage door. It helps in promoting the “invocation of a method on an object” to full object status. Basically, it encapsulates all the information needed to perform an action or trigger an event.
#Use built-in abc to implement Abstract classes and methods
from abc import ABC, abstractmethod
class Command(ABC):
"""Class Dedicated to Command"""
def __init__(self, receiver):
"""constructor method"""
self.receiver = receiver
def process(self):
"""process method"""
pass
class CommandImplementation(Command):
"""Class dedicated to Command Implementation"""
def __init__(self, receiver):
"""constructor method"""
self.receiver = receiver
def process(self):
"""process method"""
self.receiver.perform_action()
class Receiver:
"""Class dedicated to Receiver"""
def perform_action(self):
"""perform-action method"""
print('Action performed in receiver.')
class Invoker:
"""Class dedicated to Invoker"""
def command(self, cmd):
"""command method"""
self.cmd = cmd
def execute(self):
"""execute method"""
self.cmd.process()
if __name__ == "__main__":
# create Receiver object
receiver = Receiver()
cmd = CommandImplementation(receiver)
invoker = Invoker()
invoker.command(cmd)
invoker.execute()
Action performed in receiver.
Mediator Method
Mediator Method is a Behavioral Design Pattern that allows us to reduce the unordered dependencies between the objects. In a mediator environment, objects take the help of mediator objects to communicate with each other. It reduces coupling by reducing the dependencies between communicating objects. The mediator works as a router between objects and it can have it’s own logic to provide a way of communication.
Design Components:
- Mediator: It defines the interface for communication between colleague objects.
- Concrete Mediator: It implements the mediator interface and coordinates communication between colleague objects.
- Colleague: It defines the interface for communication with other colleagues
- Concrete Colleague: It implements the colleague interface and communicates with other colleagues through its mediator.
class Course(object):
"""Mediator class."""
def displayCourse(self, user, course_name):
print("[{}'s course ]: {}".format(user, course_name))
class User(object):
'''A class whose instances want to interact with each other.'''
def __init__(self, name):
self.name = name
self.course = Course()
def sendCourse(self, course_name):
self.course.displayCourse(self, course_name)
def __str__(self):
return self.name
# main method
if __name__ == "__main__":
mayank = User('Mayank') # user object
lakshya = User('Lakshya') # user object
krishna = User('Krishna') # user object
mayank.sendCourse("Data Structures and Algorithms")
lakshya.sendCourse("Software Development Engineer")
krishna.sendCourse("Standard Template Library")
Memento Method
Memento Method is a Behavioral Design pattern which provides the ability to restore an object to its previous state. Without revealing the details of concrete implementations, it allows you to save and restore the previous version of the object. It tries not to disturb the encapsulation of the code and allows you to capture and externalize an object’s internal state.
"""Memento class for saving the data"""
class Memento:
def __init__(self, file, content):
"""Constructor function"""
"""put all your file content here"""
self.file = file
self.content = content
"""It's a File Writing Utility"""
class FileWriterUtility:
def __init__(self, file):
"""Constructor Function"""
"""store the input file data"""
self.file = file
self.content = ""
"""Write the data into the file"""
def write(self, string):
self.content += string
"""save the data into the Memento"""
def save(self):
return Memento(self.file, self.content)
"""UNDO feature provided"""
def undo(self, memento):
self.file = memento.file
self.content = memento.content
"""CareTaker for FileWriter"""
class FileWriterCaretaker:
"""saves the data"""
def save(self, writer):
self.obj = writer.save()
"""undo the content"""
def undo(self, writer):
writer.undo(self.obj)
if __name__ == '__main__':
"""create the caretaker object"""
caretaker = FileWriterCaretaker()
"""create the writer object"""
writer = FileWriterUtility("GFG.txt")
"""write data into file using writer object"""
writer.write("First vision of GeeksforGeeks\n")
print(writer.content + "\n\n")
"""save the file"""
caretaker.save(writer)
"""again write using the writer """
writer.write("Second vision of GeeksforGeeks\n")
print(writer.content + "\n\n")
"""undo the file"""
caretaker.undo(writer)
print(writer.content + "\n\n")
State Method
State method is Behavioral Design Pattern that allows an object to change its behavior when there occurs a change in its internal state. It helps in implementing the state as a derived class of the state pattern interface. If we have to change the behavior of an object based on its state, we can have a state variable in the Object and use if-else condition block to perform different actions based on the state. It may be termed as the object-oriented state machine. It implements the state transitions by invoking methods from the pattern’s superclass.
class State:
"""State class: Base State class"""
def scan(self):
"""Scan the dial to the next station"""
self.pos += 1
"""check for the last station"""
if self.pos == len(self.stations):
self.pos = 0
print("Visiting... Station is {} {}".format(self.stations[self.pos], self.name))
class AmState(State):
"""Separate Class for AM state of the radio"""
def __init__(self, radio):
"""constructor for AM state class"""
self.radio = radio
self.stations = ["1250", "1380", "1510"]
self.pos = 0
self.name = "AM"
def toggle_amfm(self):
"""method for toggling the state"""
print("Switching to FM")
self.radio.state = self.radio.fmstate
class FmState(State):
"""Separate class for FM state"""
def __init__(self, radio):
"""Constriuctor for FM state"""
self.radio = radio
self.stations = ["81.3", "89.1", "103.9"]
self.pos = 0
self.name = "FM"
def toggle_amfm(self):
"""method for toggling the state"""
print("Switching to AM")
self.radio.state = self.radio.amstate
class Radio:
"""Dedicated class Radio"""
"""A radio. It has a scan button, and an AM / FM toggle switch."""
def __init__(self):
"""We have an AM state and an FM state"""
self.fmstate = FmState(self)
self.amstate = AmState(self)
self.state = self.fmstate
def toggle_amfm(self):
"""method to toggle the switch"""
self.state.toggle_amfm()
def scan(self):
"""method to scan """
self.state.scan()
# main method
if __name__ == "__main__":
# create radio object
radio = Radio()
actions = [radio.scan] * 3 + [radio.toggle_amfm] + [radio.scan] * 3
actions *= 2
for action in actions:
action()
Visiting... Station is 89.1 FM
Visiting... Station is 103.9 FM
Visiting... Station is 81.3 FM
Switching to AM
Visiting... Station is 1380 AM
Visiting... Station is 1510 AM
Visiting... Station is 1250 AM
Visiting... Station is 1380 AM
Visiting... Station is 1510 AM
Visiting... Station is 1250 AM
Switching to FM
Visiting... Station is 89.1 FM
Visiting... Station is 103.9 FM
Visiting... Station is 81.3 FM
Template Method
The Template method is a Behavioral Design Pattern that defines the skeleton of the operation and leaves the details to be implemented by the child class. Its subclasses can override the method implementations as per need but the invocation is to be in the same way as defined by an abstract class. It is one of the easiest among the Behavioral design pattern to understand and implements. Such methods are highly used in framework development as they allow us to reuse the single piece of code at different places by making certain changes. This leads to avoiding code duplication also.
def get_text():
""" method to get the text of file"""
return "plain_text"
def get_xml():
""" method to get the xml version of file"""
return "xml"
def get_pdf():
""" method to get the pdf version of file"""
return "pdf"
def get_csv():
"""method to get the csv version of file"""
return "csv"
def convert_to_text(data):
"""method used to convert the data into text format"""
print("[CONVERT]")
return "{} as text".format(data)
def saver():
"""method used to save the data"""
print("[SAVE]")
def template_function(getter, converter = False, to_save = False):
"""helper function named as template_function"""
"""input data from getter"""
data = getter()
print("Got {}".format(data))
if len(data) <= 3 and converter:
data = converter(data)
else:
print("Skip conversion")
"""saves the data only if user want to save it"""
if to_save:
saver()
print("{} was processed".format(data))
"""main method"""
if __name__ == "__main__":
template_function(get_text, to_save = True)
template_function(get_pdf, converter = convert_to_text)
template_function(get_csv, to_save = True)
template_function(get_xml, to_save = True)
Got plain_text
Skip conversion
[SAVE]
plain_text was processed
Got pdf
[CONVERT]
pdf as text was processed
Got csv
Skip conversion
[SAVE]
csv was processed
References and links
- Download the Jupyter notebook from: github.com/charlstown/Python-Design-Patterns
- Python Design Patterns course from Jungwoo Ryoo
- Jungwoo Ryoo LinkedIn profile
- More Notebooks like this here