current position:Home>Implementation of top-level design pattern in Python

Implementation of top-level design pattern in Python

2021-08-22 04:50:03 PythonCN

In software development , Design patterns are solutions to common problems in a specific context , These schemes have been verified .

Their main goal is to show us a good way to program and explain why other options don't work . ​

Use common design patterns , You can :

  • Speed up the development process ;
  • Reduce the number of lines of code ;
  • Make sure your code is well designed ;
  • Foresee the problems caused by small problems in the future .

Design patterns can significantly improve the lives of software developers , Regardless of him ( she ) What programming language is used . ​

I interviewed Jellyfish.tech Founder and CTO of 、 Have 9 More than years of experience Python Developers and software architects _ _Roman Latyshenko About his top design patterns . ​

I mainly use Python/Django, So here is what I use every day at work Python List of design patterns in .

Behavior type

Iterator pattern

Iterators allow you to iterate over the elements of a collection without exposing internal details . ​

Use scenarios . Most of the time , I use it to provide a standard way to traverse a collection . ​

Clean client code ( Principle of single responsibility ). You can introduce iterators into the collection without changing the client code ( to open up / Closed principle ). Each iteration object has its own iteration state , So you can postpone and continue iterations . Using iterators for simple collections can overload the application . ​

structure image.png

Code example

from __future__ import annotations
from collections.abc import Iterable, Iterator
from typing import Any, List


class AlphabeticalOrderIterator(Iterator):
    _position: int = None
    _reverse: bool = False

    def __init__(self, collection: WordsCollection, 
                 reverse: bool = False)
:

        self._collection = collection
        self._reverse = reverse
        self._position = -1 if reverse else 0

    def __next__(self):
        try:
            value = self._collection[self._position]
            self._position += -1 if self._reverse else 1
        except IndexError:
            raise StopIteration()
        return value


class WordsCollection(Iterable):
    def __init__(self, collection: List[Any] = []):
        self._collection = collection

    def __iter__(self) -> AlphabeticalOrderIterator:
        return AlphabeticalOrderIterator(self._collection)

    def get_reverse_iterator(self) -> AlphabeticalOrderIterator:
        return AlphabeticalOrderIterator(self._collection, True)

    def add_item(self, item: Any):
        self._collection.append(item)


if __name__ == "__main__":
    collection = WordsCollection()
    collection.add_item("First")
    collection.add_item("Second")
    collection.add_item("Third")

    print("Straight traversal:")
    print("\n".join(collection))

    print("Reverse traversal:")
    print("\n".join(collection.get_reverse_iterator()))

The state pattern

State patterns help objects change their behavior when their internal state changes . ​

Use scenarios . State mode helps me

  • Change a large number of object states .
  • Reduce the number of lines of repetitive code in similar transitions and states .
  • Avoid a large number of conditions .

Follow the principle of single responsibility : Separate the classes of code related to different states . Adding a new state does not change the context or state of the class ( open / Closed principle ). With little change in the state machine , There may be too many usage States . ​

structure image.png

Sample code

from __future__ import annotations
from abc import ABC, abstractmethod


class Context(ABC):
    _state = None
    def __init__(self, state: State):
        self.transition_to(state)
        
    def transition_to(self, state: State):
        print(f"Context: Transition to {type(state).__name__}")
        self._state = state
        self._state.context = self
        
    def request1(self):
        self._state.handle1()
        
    def request2(self):
        self._state.handle2()
        
        
class State(ABC):
    @property
    def context(self) -> Context:
        return self._context
    
    @context.setter
    def context(self, context: Context):
        self._context = context
        
    @abstractmethod
    def handle1(self):
        pass
    
    @abstractmethod
    def handle2(self):
        pass
    
    
class ConcreteStateA(State):
    def handle1(self):
        print("ConcreteStateA handles request1.")
        print("ConcreteStateA wants to change the state of the context.")
        self.context.transition_to(ConcreteStateB())
        
    def handle2(self):
        print("ConcreteStateA handles request2.")
        
        
class ConcreteStateB(State):
    def handle1(self):
        print("ConcreteStateB handles request1.")
        
    def handle2(self):
        print("ConcreteStateB handles request2.")
        print("ConcreteStateB wants to change the state of the context.")
        self.context.transition_to(ConcreteStateA())
        
        
if __name__ == "__main__":
    context = Context(ConcreteStateA())
    context.request1()
    context.request2()

Observer mode

Observers will inform them of events that occur in other objects they observe , Without having to couple to their classes . ​

Use scenarios . Every time I need to add a subscription mechanism to make objects subscribe / When unsubscribing notifications of events that occur for a particular publisher class , I use observer mode .

A good example is simply subscribing to news from any online magazine , You can usually choose your field of interest ( science 、 Digital technology, etc ). perhaps , Of e-commerce platform “ Let me know when you have goods ” Buttons are another example .

You don't have to change the publisher's code to add the subscriber's class . Subscribers receive notifications in random order . ​

structure image.png

Sample code

from __future__ import annotations
from abc import ABC, abstractmethod
from random import randrange
from typing import List


class Subject(ABC):
    @abstractmethod
    def attach(self, observer: Observer):
        pass
    
    @abstractmethod
    def detach(self, observer: Observer):
        pass
    
    @abstractmethod
    def notify(self):
        pass
    
    
class ConcreteSubject(Subject):
    _state: int = None
    _observers: List[Observer] = []
   
    def attach(self, observer: Observer):
        print("Subject: Attached an observer.")
        self._observers.append(observer)

    def detach(self, observer: Observer):
        self._observers.remove(observer)
        
    def notify(self):
        print("Subject: Notifying observers...")
        for observer in self._observers:
            observer.update(self)
            
    def some_business_logic(self):
        print("Subject: I'm doing something important.")
        self._state = randrange(010)
        print(f"Subject: My state has just changed to: {self._state}")
        self.notify()
        
        
class Observer(ABC):
    @abstractmethod
    def update(self, subject: Subject):       
        pass
    
    
class ConcreteObserverA(Observer):
    def update(self, subject: Subject):
        if subject._state < 3:
            print("ConcreteObserverA: Reacted to the event")
            
            
class ConcreteObserverB(Observer):
    def update(self, subject: Subject):
        if subject._state == 0 or subject._state >= 2:
            print("ConcreteObserverB: Reacted to the event")
            
            
if __name__ == "__main__":    
    subject = ConcreteSubject()
    observer_a = ConcreteObserverA()
    subject.attach(observer_a)
    observer_b = ConcreteObserverB()
    subject.attach(observer_b)
    subject.some_business_logic()
    subject.some_business_logic()
    subject.detach(observer_a)
    subject.some_business_logic()

Structural type

Appearance mode

The facade pattern provides a simplified but limited interface to reduce the complexity of the application . Appearance mode can “ shielding ” A complex subsystem with multiple moving parts . ​

Use scenarios . I created the appearance pattern class , In case I have to use complex libraries and API and ( or ) I only need some of their functions .

System complexity and code separation Use appearance mode , You can create a god object . ​

structure image.png

Sample code

class Addition:
    def __init__(self, field1: int, field2: int):
        self.field1 = field1
        self.field2 = field2
        
    def get_result(self):
        return self.field1 + self.field2
    
    
class Multiplication:
    def __init__(self, field1: int, field2: int):
        self.field1 = field1
        self.field2 = field2
        
    def get_result(self):
        return self.field1 * self.field2
    
    
class Subtraction:
    def __init__(self, field1: int, field2: int):
        self.field1 = field1
        self.field2 = field2
        
    def get_result(self):
        return self.field1 - self.field2
    
    
class Facade:
    @staticmethod
    def make_addition(*args) -> Addition:
        return Addition(*args)
    
    @staticmethod
    def make_multiplication(*args) -> Multiplication:
        return Multiplication(*args)
    
    @staticmethod
    def make_subtraction(*args) -> Subtraction:
        return Subtraction(*args)
    
    
if __name__ == "__main__":
    addition_obj = Facade.make_addition(55)
    multiplication_obj = Facade.make_multiplication(52)
    subtraction_obj = Facade.make_subtraction(155)
    print(addition_obj.get_result())
    print(multiplication_obj.get_result())
    print(subtraction_obj.get_result())

Decorator mode

Decorators attach new behaviors to objects without modifying their structure . ​

This pattern generates a decorator class to wrap the original class and add new functionality .

Use scenarios . Every time I need to add additional behavior to an object without entering the code , I can use decorator mode .

Change object behavior without creating subclasses . You can combine multiple behaviors by wrapping an object into multiple decorators . A particular decorator is difficult to remove from the wrapper stack . ​

structure image.png

Sample code

class my_decorator:
    def __init__(self, func):
        print("inside my_decorator.__init__()")
        func() # Prove that function definition has completed
        
    def __call__(self):
        print("inside my_decorator.__call__()")
        
        
@my_decorator
def my_function():
    print("inside my_function()")
    
    
if __name__ == "__main__":    
    my_function()

Adapter pattern

The adapter pattern serves as an intermediate class to connect functions of independent or incompatible interfaces . ​

Use scenarios . Set collaboration between interfaces , I use adapter mode to solve the problem of format incompatibility .

for example , The adapter can help to XML Data format converted to JSON For further analysis .

Allows separation of interfaces from business logic . Adding a new adapter won't break the client's code Increase code complexity

structure image.png

Sample code

class Target:
    def request(self):
        return "Target: The default target's behavior."
    
    
class Adaptee:
    def specific_request(self):
        return ".eetpadA eht fo roivaheb laicepS"
    
    
class Adapter(Target, Adaptee):
    def request(self):
        return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}"
    
    
def client_code(target: "Target"):
    print(target.request())
    
    
if __name__ == "__main__":
    print("Client: I can work just fine with the Target objects:")
    target = Target()
    client_code(target)
    adaptee = Adaptee()
    
    print("Client: The Adaptee class has a weird interface. "
          "See, I don't understand it:")
    print(f"Adaptee: {adaptee.specific_request()}")
    print("Client: But I can work with it via the Adapter:")
    
    adapter = Adapter()
    client_code(adapter)

Creation type

The singleton pattern

The singleton pattern limits a class to having multiple instances , And ensure that the global access point of the instance . ​

Use scenarios . Singleton mode helps me

  • Managing shared resources : That is, a single database shared by multiple parts of the application 、 File manager or printer spooler .
  • Store global status ( Help file path 、 User language 、 Application path, etc ).
  • Create a simple logger.

Class has only one instance It's hard to unit test code , Because most test frameworks are created mock Object using inheritance .

structure image.png

Code example

class Singleton:
    def __new__(cls):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance
    
    
if __name__ == "__main__":
    s = Singleton()
    print("Object created:", s)
    s1 = Singleton()
    print("Object created:", s1)

When to use Python Design mode of ?

When you need to work for multiple API Option provides a unified interface , Appearance mode is very useful . for example , You should integrate a payment system into your application , Leave the possibility of changing it . under these circumstances , You can use appearance mode , You just need to create a new look , Without rewriting the entire application . ​

If API Completely different , The problem here will arise , Because it's not easy to design a common interface for appearance patterns .

State is used to manage multiple independent components of an application , The premise is that the initial architecture means their independence . therefore , It may be a good idea to create a separate module for state management and use the observer pattern .

Due to the built-in support for decorators , Decorators are probably the most commonly used Python Pattern . for example , Decorators provide a convenient and explicit way to use some libraries , And create more and more rich opportunities for application design and management . This pattern also ensures a wide range of possibilities for function combinations , And found new opportunities for functional programming .

The adapter is suitable for processing a large number of data in different formats . This pattern allows one algorithm to be used for each data format instead of multiple algorithms .

Iterators have similar benefits , So they can be used together . Besides , One of the iterator variants called generators ( A long time ago in Python Introduction in ) Allow more efficient use of memory , It is very valuable for some types of projects when dealing with large amounts of data .

Last , The importance of the singleton pattern cannot be underestimated : Database connection 、API、 file …… These are the times when developers should know how to avoid mistakes in the process . Singleton mode can do well here , Not to mention the possibility of using the same instance every time instead of copying it to reduce memory consumption . ​

translate
Implementation of Top Design Patterns in Python[1]

Reference material

[1]

Implementation of Top Design Patterns in Python: https://medium.com/python-pandemonium/top-design-patterns-in-python-9778843d5451

copyright notice
author[PythonCN],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2021/08/20210822044949979R.html