LoginSignup
0
1

More than 3 years have passed since last update.

Behavioral Patterns in Python

Posted at

Observer

Observer establishes a one-to-many relationship between a subject and multiple observers. Our problem here is that a subject object need to be monitored, and other observer objects need to be notified when there is a change in the subject.

class Subject(object): #Represents what is being 'observed'

    def __init__(self):
        self._observers = [] # This 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 attach(self, observer):
        if observer not in self._observers: #If the observer is not already in the observers list
            self._observers.append(observer) # append the observer to the list

    def detach(self, observer): #Simply remove the observer
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier=None):
        for observer in self._observers: # For all the observers in the list
            if modifier != observer: # Don't notify the observer who is actually updating the temperature 
                observer.update(self) # Alert the observers!

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 #Initialize 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):
        self._temp = temp
        self.notify() #Notify the observers whenever somebody changes the core temperature

class TempViewer:

    def update(self, subject): #Alert method that is invoked when the notify() method in a concrete subject is invoked
        print("Temperature Viewer: {} has Temperature {}".format(subject._name, subject._temp))

#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

Visitor

It is sometimes necessary to add new operations dynamically to existing classes with minimal changes.

class House(object): #The class being visited 
    def accept(self, visitor):
        """Interface to accept a visitor"""
        visitor.visit(self) #Triggers the visiting operation!

    def work_on_hvac(self, hvac_specialist):
        print(self, "worked on by", hvac_specialist) #Note that we now have a reference to the HVAC specialist object in the house object!

    def work_on_electricity(self, electrician):
        print(self, "worked on by", electrician) #Note that we now have a reference to the electrician object in the house object!

    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): #Inherits from the parent class, Visitor
    """Concrete visitor: HVAC specialist"""
    def visit(self, house):
        house.work_on_hvac(self) #Note that the visitor now has a reference to the house object


class Electrician(Visitor): #Inherits from the parent class, Visitor
    """Concrete visitor: electrician"""
    def visit(self, house):
        house.work_on_electricity(self) #Note that the visitor now has a reference to the house object

#Create an HVAC specialist
hv = HvacSpecialist()
#Create an electrician
e = Electrician()

#Create a house
home = House()

#Let the house accept the HVAC specialist and work on the house by invoking the visit() method
home.accept(hv)

#Let the house accept the electrician and work on the house by invoking the visit() method
home.accept(e)

Iterator

The iterator pattern allows a client to have sequential access to the elements of an aggregate object without exposing its underlying structure. The problem is that some programmers overcrowd the traversal interfaces of an aggregate object for every possible way of iteration.

def count_to(count):
    """Our iterator implementation"""

    #Our list
    numbers_in_german = ["eins", "zwei", "drei", "vier", "funf"]

    #Our built-in iterator
    #Creates a tuple such as (1, "eins")
    iterator = zip(range(count), numbers_in_german)

    #Iterate through our iterable list
    #Extract the German numbers
    #Put them in a generator called number
    for position, number in iterator:

        #Returns a 'generator' containing numbers in German
        yield number 

#Let's test the generator returned by our iterator
for num in count_to(3):
    print("{}".format(num))

for num in count_to(4):
    print("{}".format(num))

Strategy

The Strategy pattern offers a family of interchangeable algorithms to a client. The problem we often see is that there is a need for dynamically changing the behavior of an object. So we offer our Strategy class with its default behavior. When there is a need, we provide another variation of the Strategy class by dynamically replacing its default method with a new one. Python allows adding methods dynamically by importing the types module.

import types #Import the 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): #This gets replaced by another version if another strategy is provided.
        """The defaut method that prints the name of the strategy being used"""
        print("{} is used!".format(self.name))

#Replacement method 1
def strategy_one(self):
    print("{} is used to execute method 1".format(self.name))

#Replacement method 2    
def strategy_two(self):
    print("{} is used to execute method 2".format(self.name))

#Let's create our default strategy
s0 = Strategy()
#Let's execute our default strategy
s0.execute()

#Let's create the first varition 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()

s2 = Strategy(strategy_two)
s2.name = "Strategy Two"
s2.execute()

Chain of Responsibility

Chain of Responsibility opens up various possibilities of processing for a given request. The Chain of Responsibility pattern decouples the request and its processing. Our given problem is that many different types of processing needs to be done depending on what the request is.

class Handler: #Abstract handler
    """Abstract Handler"""
    def __init__(self, successor):
        self._successor = successor # Define who is the next handler

    def handle(self, request):
            handled = self._handle(request) #If handled, stop here

            #Otherwise, keep going
            if not handled:
                self._successor.handle(request) 

    def _handle(self, request):
        raise NotImplementedError('Must provide implementation in subclass!')

class ConcreteHandler1(Handler): # Inherits from the abstract handler
    """Concrete handler 1"""
    def _handle(self, request):
        if 0 < request <= 10: # Provide a condition for handling
            print("Request {} handled in handler 1".format(request))
            return True # Indicates that the request has been handled

class DefaultHandler(Handler): # Inherits from the abstract handler
    """Default handler"""

    def _handle(self, request):
        """If there is no handler available"""
        #No condition checking since this is a default handler
        print("End of chain, no handler for {}".format(request))
        return True # Indicates that the request has been handled

class Client: # Using handlers
    def __init__(self):
        self.handler = ConcreteHandler1(DefaultHandler(None)) # Create handlers and use them in a sequence you want
                                                              # Note that the default handler has no successor

    def delegate(self, requests): # Send your requests one at a time for handlers to handle
        for request in requests:
                self.handler.handle(request)

# Create a client
c = Client()

# Create requests
requests = [2, 5, 30]

# Send the requests
c.delegate(requests)

More Design Patterns in Python

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1