Design mode and python implementation
Keywords:
PHP
shell
Mobile
Python
Programming
Design Patterns and Python Implementation
What is the design pattern?
Christopher Alexander: "Each pattern describes a problem that is recurring around us and the core of the solution to that problem.This allows you to use the program again and again without having to repeat your work."
Design patterns are summarized and optimized reusable solutions to some programming problems that we often encounter.A design pattern does not act directly on our code like a class or library.Conversely, design patterns are more advanced and are a method template that must be implemented in specific situations.Design patterns do not bind specific programming languages.A good design pattern should be implemented in most programming languages (depending on language characteristics, if not all).Most importantly, design patterns are also a double-edged sword, which can cause disasters and endless troubles if used in inappropriate situations.However, if design patterns are used in the right place at the right time, it will be your savior.
At first, you'll think of a "pattern" as a particularly smart way to solve a particular type of problem.That's right. It does look like the most versatile and flexible solution that comes from seeing things from different perspectives and working together with a lot of people.Maybe you've seen or solved these problems before, but your solution is probably not as complete as the pattern.
Although called design patterns, they are not closely related to the design domain.Design patterns are different from traditional analysis, design and implementation. In fact, design patterns root a complete idea in the program, so it may appear in the analysis stage or higher design stage.It's interesting because the design pattern is specific to program code, so you might think that it won't appear before the implementation phase (in fact, you're not aware you're using the specific design pattern until you enter the implementation phase).
You can understand patterns through the basic concepts of programming: adding an abstraction layer.An abstract thing is to isolate any specific details in order to separate those unchanged core parts from other details.When you find that parts of your program are often changed for some reason, and you don't want those parts to cause other parts to change, you need to think about design methods that won't change.Doing so not only makes the code more maintainable, but also makes it easier to understand, reducing development costs.
Three basic design patterns:
-
Create patterns, provide instantiation methods, and provide appropriate object creation methods for the appropriate situation.
-
Structured patterns, often used to handle relationships between entities, allow them to work together better.
-
Behavior patterns are used to build communication between different entities, providing easier and more flexible methods of communication between entities.
Six Principles of Design Mode
- Open and Close Principle: A software entity such as classes, modules, and functions should be open to extensions and closed to modifications.That is, the software entity should expand as far as possible without modifying the original code.
- Liskov Replacement Principle: All objects that reference a base class (parent class) must be able to use its subclasses transparently.
- Dependency Inversion Principle: High-level modules should not depend on low-level modules, and both should depend on their abstraction; abstraction should not depend on details; and detail should depend on abstraction.In other words, program interfaces, not implementations.
- Interface Isolation Principle: Use multiple specialized interfaces instead of a single overall interface, that is, clients should not rely on interfaces that they do not need.
- Dimitt's rule: A software entity should interact with as few other entities as possible.
- Single Responsibility Principle: Do not have more than one reason for a class change.In general, a class is responsible for only one responsibility.(
Interface
Interface: A special class that declares several methods that must be implemented by a class that inherits the interface.
Role: Limits the name and invocation of methods of classes that inherit interfaces; hides the internal implementation of classes.
An interface is an abstract base class (parent class), and a class that restricts its inheritance must implement some of the methods defined in the interface.
Python uses ABCMeta, abstractmethod's Abstract class, and abstract method to implement the function of the interface.Interface class defines a method, which is not implemented specifically. Restriction subclasses must have this method.Implement specific functionality in interface subclasses.
# Using abstract classes and methods for abstraction
from abc import ABCMeta
from abc import abstractmethod # Import abstract methods
class Father(metaclass=ABCMeta): # Create abstract classes
@abstractmethod
def f1(self):
pass
@abstractmethod
def f2(self):
pass
class F1(Father):
def f1(self):
pass
def f2(self):
pass
def f3(self):
pass
obj = F1()
class Interface:
def method(self, arg):
raise NotImplementedError
Error Definition Interface
Creative mode
1. Simple Factory Mode
Content: Instead of exposing the implementation details of object creation directly to the client, an instance of the product class is created through a factory class.
Roles:
- Factory Role (Creator)
- Abstract Product Role
- Specific Product Role
Advantage:
- Hide implementation details of object creation
- Client does not need to modify code
Disadvantages:
- Violating the single responsibility principle, the creation logic is federated into one factory class
- When adding a new product, the factory class code needs to be modified, which violates the open and close principle
from abc import abstractmethod, ABCMeta
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def __init__(self, enable_yuebao=False):
self.enable_yuebao = enable_yuebao
def pay(self, money):
if self.enable_yuebao:
print("Payment policy%s element" % money)
else:
print("Alipay Payment%s element" % money)
class ApplePay(Payment):
def pay(self, money):
print("Apple Payment%s element" % money)
class PaymentFactory:
def create_payment(self, method):
if method == "alipay":
return Alipay()
elif method == 'yuebao':
return Alipay(enable_yuebao=True)
elif method == "applepay":
return ApplePay()
else:
raise NameError(method)
f = PaymentFactory()
p = f.create_payment("yuebao")
p.pay(100)
PaymentFactory Simple Factory
2. Factory Method
Content: Define an interface (factory interface) for creating objects so that subclasses decide which product class to instantiate.
Roles:
- Abstract Factory Role (Creator)
- Specific Factory Role (Concrete Creator)
- Abstract Product Role
- Specific Product Role
The factory method mode corresponds to a specific factory for each specific product compared to the simple factory mode.
Scenarios applicable:
- When many, many complex objects need to be produced.
- When coupling needs to be reduced.
- When the product type in the system needs to be expanded frequently.
Advantage:
- Each specific product corresponds to a specific factory class, and there is no need to modify the factory class code
- Hide implementation details of object creation
Disadvantages:
- For each additional specific product class, a corresponding specific factory class must be added
from abc import abstractmethod, ABCMeta
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def pay(self, money):
print("Alipay Payment%s element" % money)
class ApplePay(Payment):
def pay(self, money):
print("Apple Payment%s element" % money)
class PaymentFactory(metaclass=ABCMeta):
@abstractmethod
def create_payment(self):
pass
class AlipayFactory(PaymentFactory):
def create_payment(self):
return Alipay()
class ApplePayFactory(PaymentFactory):
def create_payment(self):
return ApplePay()
af = AlipayFactory()
ali = af.create_payment()
ali.pay(120)
Factory method
3. Abstract Factory Method
Content: Define a factory class interface that allows a factory subclass to create a series of related or interdependent objects.
Example: To produce a mobile phone, there are three kinds of objects: the mobile phone shell, CPU and operating system. Each kind of object has different kinds.For each specific factory, three objects are needed to produce a mobile phone.
Roles:
- Abstract Factory Role (Creator)
- Specific Factory Role (Concrete Creator)
- Abstract Product Role
- Specific Product Role
- Client
Each specific factory in the abstract factory model produces a set of products compared to the factory method model.
Scenarios applicable:
- When the system is to be independent of the creation and combination of products
- Emphasize the design of a series of related product objects for joint use
- Provides a product class library to hide specific implementations of a product
Advantage:
- Separate the client from the specific implementation of the class
- Each factory creates a complete product line, making it easy to exchange product lines
- Favor product consistency (i.e. constraints between products)
Disadvantages:
- It is difficult to support a new kind of (abstract) product
from abc import abstractmethod, ABCMeta
# ------Abstract product------
class PhoneShell(metaclass=ABCMeta):
@abstractmethod
def show_shell(self):
pass
class CPU(metaclass=ABCMeta):
@abstractmethod
def show_cpu(self):
pass
class OS(metaclass=ABCMeta):
@abstractmethod
def show_os(self):
pass
# ------Abstract factory------
class PhoneFactory(metaclass=ABCMeta):
@abstractmethod
def make_shell(self):
pass
@abstractmethod
def make_cpu(self):
pass
@abstractmethod
def make_os(self):
pass
# ------Specific products------
class SmallShell(PhoneShell):
def show_shell(self):
print("Small mobile phone shell")
class BigShell(PhoneShell):
def show_shell(self):
print("Large mobile phone shell")
class AppleShell(PhoneShell):
def show_shell(self):
print("Apple mobile phone shell")
class SnapDragonCPU(CPU):
def show_cpu(self):
print("snapdragon CPU")
class MediaTekCPU(CPU):
def show_cpu(self):
print("Associative Family CPU")
class AppleCPU(CPU):
def show_cpu(self):
print("Apple CPU")
class Android(OS):
def show_os(self):
print("Android system")
class IOS(OS):
def show_os(self):
print("iOS system")
# ------Specific factories------
class MiFactory(PhoneFactory):
def make_cpu(self):
return SnapDragonCPU()
def make_os(self):
return Android()
def make_shell(self):
return BigShell()
class HuaweiFactory(PhoneFactory):
def make_cpu(self):
return MediaTekCPU()
def make_os(self):
return Android()
def make_shell(self):
return SmallShell()
class IPhoneFactory(PhoneFactory):
def make_cpu(self):
return AppleCPU()
def make_os(self):
return IOS()
def make_shell(self):
return AppleShell()
# ------Client------
class Phone:
def __init__(self, cpu, os, shell):
self.cpu = cpu
self.os = os
self.shell = shell
def show_info(self):
print("Text message:")
self.cpu.show_cpu()
self.os.show_os()
self.shell.show_shell()
def make_phone(factory):
cpu = factory.make_cpu()
os = factory.make_os()
shell = factory.make_shell()
return Phone(cpu, os, shell)
p1 = make_phone(HuaweiFactory())
p1.show_info()
Abstract factory
4. Builder Mode
Content: Separates the construction of a complex object from its representation so that the same construction process can create different representations.
Roles:
- Builder
- Concrete Builder
- Director
- Product
Builder mode is similar to abstract factory mode and is used to create complex objects.The main difference is that the builder pattern focuses on building a complex object step by step, while the abstract factory pattern focuses on a number of series of product objects.
Scenarios applicable:
- When the algorithm for creating a complex object (Director) should be independent of its components and how they are assembled (Builder)
- When the construction process allows different representations of the constructed object (different Builder s).
Advantage:
- Hide the internal structure and assembly process of a product
- Separate construction code from representation code
- More fine-grained control of the construction process is possible
from abc import abstractmethod, ABCMeta
# ------product------
class Player:
def __init__(self, face=None, body=None, arm=None, leg=None):
self.face = face
self.arm = arm
self.leg = leg
self.body = body
def __str__(self):
return "%s, %s, %s, %s" % (self.face, self.arm, self.body, self.leg)
# ------Builder------
class PlayerBuilder(metaclass=ABCMeta):
@abstractmethod
def build_face(self):
pass
@abstractmethod
def build_arm(self):
pass
@abstractmethod
def build_leg(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def get_player(self):
pass
class BeautifulWomanBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = "Pretty Face"
def build_arm(self):
self.player.arm = "Thin arm"
def build_body(self):
self.player.body = "slender waist"
def build_leg(self):
self.player.leg = "Long legged"
def get_player(self):
return self.player
class PlayerDirector:
def build_player(self, builder):
builder.build_body()
builder.build_arm()
builder.build_leg()
builder.build_face()
return builder.get_player()
director = PlayerDirector()
builder = BeautifulWomanBuilder()
p = director.build_player(builder)
print(p)
Builder pattern
5. Singleton
Content: Ensure that a class has only one instance and provide a global access point to access it.
Roles:
Scenarios applicable
- When a class can only have one instance and a customer can access it from a well-known access point
Advantage:
- Controlled access to a unique instance
- The singleton is equivalent to a global variable, but prevents namespace contamination.
Concepts similar to singleton mode functionality: global variables, static variables (methods)
Realization:
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, )
return cls._instance
class MyClass(Singleton):
a = 1
def __init__(self, name):
self.name = name
one = MyClass('egon')
two = MyClass('alex')
print(id(one))
print(id(two))
print(one == two)
print(one is two)
1. Using the u new_u method
def singleton(cls, *args, **kw):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return get_instance
@singleton
class MyClass2:
a = 1
one = MyClass2()
two = MyClass2()
print(id(one)) # 31495472
print(id(two)) # 31495472
print(one == two)
print(one is two)
2. Decorator methods
# Python The module is a natural singleton pattern.
# module_name.py
class MySingleton(object):
def foo(self):
print('danli')
my_singleton = MySingleton()
# to use
from .module_name import my_singleton
my_singleton.foo()
print(id(my_singleton))
from .module_name import my_singleton
my_singleton.foo()
print(id(my_singleton))
3. import method
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# Python2
# class MyClass:
# __metaclass__ = Singleton
# Python3
class MyClass(metaclass=Singleton):
pass
one = MyClass()
two = MyClass()
print(id(one))
print(id(two))
print(one == two)
print(one is two)
4. Use metaclass
6. Prototype
Content: Specify the kind of objects created with prototype instances, and create new objects by copying them.
Use scenarios:
- By dynamic loading;
- To avoid creating a factory class hierarchy parallel to the product class hierarchy;
- When an instance of a class can only have one of several different combinations of states.It may be easier to prototype and clone a corresponding number of them than to manually instantiate the class each time in the appropriate state.
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 and update inner attributes dictionary"""
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attr)
return obj
def main():
class A:
def __str__(self):
return "I am A"
a = A()
prototype = Prototype()
prototype.register_object('a', a)
b = prototype.clone('a', a=1, b=2, c=3)
print(a)
print(b.a, b.b, b.c)
if __name__ == '__main__':
main()
Prototype mode
Creative Mode Summary
Designs using abstract factories, prototypes, or builders are even more flexible than those using factory methods, but they are also more complex.Typically, design begins with a Factory Method.And when designers find that they need more flexibility, they think about other creation models.When you weigh design criteria against each other, understanding multiple patterns can give you more choices.
Creative Patterns Dependent on Inheritance: Factory Method Patterns
Creative Patterns Dependent on Combination: Abstract Factory Patterns, Creator Patterns
Structural patterns
1. Adapter mode (Adapter Class/Object)
Content: Convert the interface of one class to another that the client wants.Adapter mode allows classes that would otherwise not work together due to incompatible interfaces to work together.
Roles:
- Target Interface
- Classes to be adapted (Adaptee)
- Adapter
Two implementations:
- Class adapters: use multiple inheritance
- Object adapter: using composition
Scenarios applicable:
- You want to use a class that already exists and its interface does not meet your requirements
- (Object Adapter) wants to use some subclasses that already exist, but it is not possible to subclass each to match their interfaces.An object adapter can adapt to its parent interface.
Class adapter:
- Match the Adaptee and Target with a specific Adapter class.The result is that when we want to match a class and all its subclasses, the class Adaptee will not be competent.
- This allows the Adapter to redefine some of the Adaptee's behavior because the Adapter is a subclass of the Adaptee.
- Introducing only one object does not require additional pointers to indirectly get an Adaptee.
Object Adapter:
- Allows an Adapter to work with multiple Adaptees - that is, the Adaptee itself and all its subclasses, if any.The Adapter can also add functionality to all Adaptees at once.
- This makes it difficult to redefine the behavior of the Adaptee.This wine needs to generate a subclass of the Adaptee and have the Adapter reference this subclass instead of the Adaptee itself.
from abc import abstractmethod, ABCMeta
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
raise NotImplementedError
class Alipay(Payment):
def pay(self, money):
print("Alipay Payment%s element" % money)
class ApplePay(Payment):
def pay(self, money):
print("Apple Payment%s element" % money)
# ------Class to be adapted------
class WechatPay:
def cost(self, money):
print("WeChat Payment%s element" % money)
# Class Adapter
class RealWechatPay(WechatPay, Payment):
def pay(self, money):
return self.cost(money)
# object adapter
class RealWechatPay2(Payment):
def __init__(self):
self.payment = WechatPay()
def pay(self, money):
return self.payment.cost(money)
p = RealWechatPay2()
p.pay(111)
Adapter
2. Composite
Content: Groups objects into a tree structure to represent a "part-whole" hierarchy.Combination mode makes the use of individual and combined objects consistent.
Roles:
- Component
- Leaf Component
- Composite
- Client
Scenarios applicable:
- Represents a "part-whole" hierarchy of objects (especially recursive)
- You want users to ignore the difference between a composite object and a single object and use all objects in the composite structure in a unified way
Advantage:
- A class hierarchy containing basic and composite objects is defined
- Simplify client code, that is, clients can consistently use composite and individual objects
- Easier to add new types of components
Disadvantages:
- It is difficult to limit the components in a combination
from abc import abstractmethod, ABCMeta
class Graphic(metaclass=ABCMeta):
@abstractmethod
def draw(self):
pass
@abstractmethod
def add(self, graphic):
pass
def getchildren(self):
pass
# Primitives
class Point(Graphic):
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print(self)
def add(self, graphic):
raise TypeError
def getchildren(self):
raise TypeError
def __str__(self):
return "spot(%s, %s)" % (self.x, self.y)
class Line(Graphic):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def draw(self):
print(self)
def add(self, graphic):
raise TypeError
def getchildren(self):
raise TypeError
def __str__(self):
return "line segment[%s, %s]" % (self.p1, self.p2)
class Picture(Graphic):
def __init__(self):
self.children = []
def add(self, graphic):
self.children.append(graphic)
def getchildren(self):
return self.children
def draw(self):
print("------Composite Graphics------")
for g in self.children:
g.draw()
print("------END------")
pic1 = Picture()
point = Point(2,3)
pic1.add(point)
pic1.add(Line(Point(1,2), Point(4,5)))
pic1.add(Line(Point(0,1), Point(2,1)))
pic2 = Picture()
pic2.add(Point(-2,-1))
pic2.add(Line(Point(0,0), Point(1,1)))
pic = Picture()
pic.add(pic1)
pic.add(pic2)
pic.draw()
#pic1.draw()
#point.draw()
combination
3. Proxy mode
Content: Provides a proxy for other objects to control access to this object.
Roles:
- Abstract entity (Subject)
- Entity (RealSubject)
- Proxy
Scenarios applicable:
- Remote proxy: Provides proxy for remote objects
- Virtual Agent: Create large objects as needed
- Protection Agent: Controls access to the original object when the object has different access rights
Advantage:
- Remote proxy: You can hide the fact that the object is in a remote address space
- Virtual Agent: Optimizable, such as creating objects on demand
- Protection agent: Allows additional internal processing when accessing an object
from abc import ABCMeta, abstractmethod
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
def set_content(self, content):
pass
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
print("read%s File Content" % filename)
f = open(filename)
self.__content = f.read()
f.close()
def get_content(self):
return self.__content
def set_content(self, content):
f = open(self.filename, 'w')
f.write(content)
self.__content = content
f.close()
# ---Remote Agent
class ProxyA(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename)
def get_content(self):
return self.subj.get_content()
def set_content(self, content):
return self.subj.set_content(content)
# ---Virtual agent
class ProxyB(Subject):
def __init__(self, filename):
self.filename = filename
self.subj = None
def get_content(self):
if not self.subj:
self.subj = RealSubject(self.filename)
return self.subj.get_content()
x = ProxyB('abc.txt')
# print(x.get_content())
# ---Protection Agent
class ProxyC(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename)
def get_content(self):
self.subj.get_content()
def set_content(self, content):
raise PermissionError
# filename = "abc.txt"
# username = input()
# if username!="alex":
# p = ProxyC(filename)
# else:
# p = ProxyA(filename)
#
# print(p.get_content())
proxy pattern
Behavior patterns
1. Chain of Responsibility
Content: Enables multiple objects to process requests, thereby avoiding coupling between the sender and receiver of the request.Join these objects into a chain and pass the request along the chain until one object processes it.
Roles:
- Handler
- ConcreteHandler
- Client
Example:
- Leave Department approval: leader - > Department Manager - > General Manager
- Javascript Event Floating Mechanism
Scenarios applicable:
- There are multiple objects that can handle a request, and which one is handled by the runtime
- Submit a request to one of multiple objects without an explicit recipient
Advantage:
- Decrease coupling: An object does not need to know which other object handles its request
Disadvantages:
- Request not guaranteed to be received: end of chain not processed or chain configuration error
from abc import ABCMeta, abstractmethod
class Handler(metaclass=ABCMeta):
@abstractmethod
def handle_leave(self, day):
pass
class GeneralManagerHandler(Handler):
def handle_leave(self, day):
if day < 10:
print("General Manager Approval%d Day Holiday" % day)
return True
else:
print("Ha-ha")
return False
class DepartmentManagerHandler(Handler):
def __init__(self):
self.successor = GeneralManagerHandler()
def handle_leave(self, day):
if day < 7:
print("Approval by Department Manager%d Day Holiday" % day)
return True
else:
print("Department manager is not authorized to grant leave")
return self.successor.handle_leave(day)
class ProjectDirectorHandler(Handler):
def __init__(self):
self.successor = DepartmentManagerHandler()
def handle_leave(self, day):
if day < 3:
print("Project Supervisor Approval%d Day Holiday" % day)
return True
else:
print("Project Supervisor Has No Right to Leave")
return self.successor.handle_leave(day)
day = 11
h = ProjectDirectorHandler()
print(h.handle_leave(day))
Leave process
# --Advanced examples--imitate js event processing
from abc import ABCMeta, abstractmethod
class Handler(metaclass=ABCMeta):
@abstractmethod
def add_event(self, func):
pass
@abstractmethod
def handle(self):
pass
class BodyHandler(Handler):
def __init__(self):
self.func = None
def add_event(self, func):
self.func = func
def handle(self):
if self.func:
return self.func()
else:
print("The last level is reached and cannot be processed")
class ElementHandler(Handler):
def __init__(self, successor):
self.func = None
self.successor = successor
def add_event(self, func):
self.func = func
def handle(self):
if self.func:
return self.func()
else:
return self.successor.handle()
# Client
# <body><div><a>
body = {'type': 'body', 'name': 'body', 'children': [], 'father': None}
div = {'type': 'div', 'name': 'div', 'children': [], 'father': body}
a = {'type': 'a', 'name': 'a', 'children': [], 'father': div}
body['children'].append(div)
div['children'].append(a)
# print(body)
body['event_handler'] = BodyHandler()
div['event_handler'] = ElementHandler(div['father']['event_handler'])
a['event_handler'] = ElementHandler(a['father']['event_handler'])
def attach_event(element, func):
element['event_handler'].add_event(func)
# test
def func_a():
print("This is for a Functions of")
def func_div():
print("This is for div Functions of")
def func_body():
print("This is for body Functions of")
attach_event(a, func_a)
attach_event(div, func_div)
attach_event(body, func_body)
a['event_handler'].handle()
Mimic js event handling
2. Iterator mode
Content: Provides a way to access elements in an aggregated object sequentially without exposing its internal representation.
Scenarios applicable:
- Access the contents of an aggregated object without exposing its internal representation.
- Supports multiple traversals of aggregated objects.
- Provides a unified interface for traversing different aggregation structures (that is, supports polymorphic iteration)
Implementation method: u iter_u, u next_u
3. Observer mode
Content: Defines a one-to-many dependency between objects. When the state of an object changes, all objects that depend on it are notified and updated automatically.The observer mode is also known as the publish-subscribe mode
Roles:
- Subject
- ConcreteSubject - Publisher
- Abstract Observer
- ConcreteObserver - Subscriber
Scenarios applicable:
- When an abstract model has two aspects, one depends on the other.Encapsulate the two in separate objects so that they can be changed and reused independently of each other.
- When changes to one object require changes to other objects at the same time, it is not known how many objects need to be changed.
- When an object must notify other objects, it cannot assume who the other objects are.In other words, you don't want these objects to be tightly coupled.
Advantage:
- Minimal Abstract coupling between target and observer
- Supports broadcast communications
Disadvantages:
- Multiple observers do not know each other exists, so an observer's modification to the subject may cause incorrect updates.
from abc import ABCMeta, abstractmethod
class Observer(metaclass=ABCMeta):
@abstractmethod
def update(self, notice):
pass
class Notice:
def __init__(self):
self.observers = []
def attach(self, obs):
self.observers.append(obs)
def detach(self, obs):
self.observers.remove(obs)
# obs.company_info=None
def notify(self):
for obj in self.observers:
obj.update(self)
class ManagerNotice(Notice):
def __init__(self, company_info=None):
super().__init__()
self.__company_info = company_info
def detach(self, obs):
super().detach(obs)
obs.company_info = None
@property
def company_info(self):
return self.__company_info
@company_info.setter
def company_info(self, info):
self.__company_info = info
self.notify()
class Manager(Observer):
def __init__(self):
self.company_info = None
def update(self, noti):
self.company_info = noti.company_info
notice = ManagerNotice()
alex = Manager()
wusir = Manager()
print(alex.company_info)
print(wusir.company_info)
notice.attach(alex)
notice.attach(wusir)
notice.company_info = "The company is running well"
print(alex.company_info)
print(wusir.company_info)
notice.company_info = "The company is going public"
print(alex.company_info)
print(wusir.company_info)
notice.detach(wusir)
notice.company_info = "The company is going bankrupt. Run fast"
print(alex.company_info)
print(wusir.company_info)
notice.company_info = "The company has gone bankrupt"
print(alex.company_info)
print(wusir.company_info)
Publisher-Subscriber
4. Strategy
Content: Define a series of algorithms, encapsulate them one by one, and make them interchangeable.This mode allows the algorithm to change independently of the users who use it.
Roles:
- Strategy
- ConcreteStrategy
- Context
Scenarios applicable:
- Many related classes simply behave differently
- Different variants requiring the use of an algorithm
- The algorithm uses data that the client does not need to know
- Multiple behaviors in a class exist as multiple conditional statements, which can be encapsulated in different policy classes.
Advantage:
- Defines a series of reusable algorithms and behaviors
- Eliminated some conditional statements
- Different implementations of the same behavior can be provided
Disadvantages:
- Customers must understand different strategies
- Communication overhead between policy and context
- Increased number of objects
from abc import ABCMeta, abstractmethod
import random
class Sort(metaclass=ABCMeta):
@abstractmethod
def sort(self, data):
pass
class QuickSort(Sort):
def quick_sort(self, data, left, right):
if left < right:
mid = self.partition(data, left, right)
self.quick_sort(data, left, mid - 1)
self.quick_sort(data, mid + 1, right)
def partition(self, data, left, right):
tmp = data[left]
while left < right:
while left < right and data[right] >= tmp:
right -= 1
data[left] = data[right]
while left < right and data[left] <= tmp:
left += 1
data[right] = data[left]
data[left] = tmp
return left
def sort(self, data):
print("Quick Sort")
return self.quick_sort(data, 0, len(data) - 1)
class MergeSort(Sort):
def merge(self, data, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if data[i] <= data[j]:
ltmp.append(data[i])
i += 1
else:
ltmp.append(data[j])
j += 1
while i <= mid:
ltmp.append(data[i])
i += 1
while j <= high:
ltmp.append(data[j])
j += 1
data[low:high + 1] = ltmp
def merge_sort(self, data, low, high):
if low < high:
mid = (low + high) // 2
self.merge_sort(data, low, mid)
self.merge_sort(data, mid + 1, high)
self.merge(data, low, mid, high)
def sort(self, data):
print("Merge Sort")
return self.merge_sort(data, 0, len(data) - 1)
class Context:
def __init__(self, data, strategy=None):
self.data = data
self.strategy = strategy
def set_strategy(self, strategy):
self.strategy = strategy
def do_strategy(self):
if self.strategy:
self.strategy.sort(self.data)
else:
raise TypeError
li = list(range(100000))
random.shuffle(li)
context = Context(li, MergeSort())
context.do_strategy()
# print(context.data)
random.shuffle(context.data)
context.set_strategy(QuickSort())
context.do_strategy()
Policy Mode
5. Template Method
Content: Defines the skeleton of the algorithm in an operation, delaying some steps to subclasses.Template methods allow subclasses to redefine certain steps of an algorithm without changing its structure.
Roles:
- AbstractClass: Defines an abstract atomic operation (hook operation); implements a template method as the framework of the algorithm.
- ConcreteClass: Implement atomic operations
Scenarios applicable:
- Implement the invariant part of an algorithm at once
- Public behavior in each subclass should be extracted and aggregated into a common parent class to avoid code duplication
- Control Subclass Extension
from abc import ABCMeta, abstractmethod
class IOHandler(metaclass=ABCMeta):
@abstractmethod
def open(self, name):
pass
@abstractmethod
def deal(self, change):
pass
@abstractmethod
def close(self):
pass
def process(self, name, change):
self.open(name)
self.deal(change)
self.close()
class FileHandler(IOHandler):
def open(self, name):
self.file = open(name, "w")
def deal(self, change):
self.file.write(change)
def close(self):
self.file.close()
f = FileHandler()
f.process("abc.txt", "Hello World")
Template Method
Reference:
Twenty-three design patterns and their python implementation - Li Qiongyu
Posted by artied on Fri, 02 Aug 2019 19:40:21 -0700