Python 设计模式实践指南

Python 常见设计模式(单例、工厂、观察者等)的代码示例与实践总结,帮助写出更可维护的工程代码。

设计模式是构建大型软件系统最强大的方法之一。


常见的设计模式

  • 创建型模式
    • 运行机制基于对象的创建方式
    • 将对象创建的细节隔离
    • 代码与创建对象的类型无关 单例模式是创建模式的一个例子
  • 结构型模式
    • 致力于设计出能够通过组合获得更强大功能的对象和类的结构
    • 重点是简化并识别类和对象之间的关系
    • 关注类的继承和组合 适配器模式是结构型模式的一个例子
  • 行为型模式
    • 关注对象之间的交互以及对象的响应性
    • 对象能够交互,同时保持松散耦合 观察者模式是行为型模式的一个例子

创建型模式

结构型设计模式

了解:

  • 描述如何将对象和类组合成更大的结构。
  • 能够简化设计工作的模式,因为能够找出更简单的方法来认识或表示实体间的关系。
  • 类模式可以通过集成来描述抽象,从而提供更有用的程序接口,而对象模式则描述了如何将对象联系起来。结构型模式是类和对象模式的综合体。

例子:

  • 适配器模式: 将一个接口转换成客户希望的另外一个接口。试图根据客户端的需求来匹配不同类的接口
  • 桥接模式: 该模式将对象的接口与其实现进行解耦,使得两者可以独立工作
  • 装饰器模式: 该模式允许在运行时或以动态方式为对象添加职责。可以通过接口给对象添加某些属性

行为型设计模式


单例模式

  • 确保类有且只有一个对象被创建
  • 为对象提供一个访问点,以是程序可以全局访问该对象
  • 控制共享资源的并行访问

实现方法

1、构造函数私有化

  • 单例模式的一个简单方法是:使构造函数私有化,并创建一个静态方法来完成对象的初始化。

    • 只允许 Singleton 类生成一个实例
    • 如果已经有一个实例,会重复提供同一个对象
    # 通过覆盖 __new__ 方法(python 用于实例化对象的特殊方法)来控制对象的创建,
    # 在创建对象之前,会检查对象是否存在
    class Singleton(object):
        def __new__(cls):
            if not hasattr(cls, 'instance'):
                cls.instance = super().__new__(cls)
            return cls.instance
    s = Singleton()
    s1 = Singleton()
    print(s, s1)
      <__main__.Singleton object at 0x000002769538A828>, <__main__.Singleton object at 0x000002769538A828>

2、懒汉式实例化

  • 能够确保在实际需要时才创建对象
    • 节约资源
class Singleton:
    __instance = None
    def __init__(self):
        if not Singleton.__instance:
            print("__init__ method called...")
        else:
            print("Instance already created;", self.get_instance())
    @classmethod
    def get_instance(cls):
        if not cls.__instance:
            cls.__instance = Singleton()
        return cls.__instance
s = Singleton()  # class initialized, but object not created
print("Object created", Singleton.get_instance())  # Object gets created here
s1 = Singleton()  # instance already created

3、模块级别的单例模式

  • 默认情况下,所有的模块都是单例,这是由 Python 的导入行为所决定的
    • 检查一个 Python 模块是否已经导入
    • 如果已经导入,则返回改模块的对象。如果还没有导入,则导入该模块,并实例化
    • 因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候, 它不会再次初始化,因为单例模式只能有一个对象,所以,他会返回同一个对象

4、Monostate(单态)单例模式

  • 所有对象共享相同状态
    # python 使用 __dict__ 存储一个类所有对象的状态
    class Borg:
        __shared_state = {'1': '2'}
        def __init__(self):
            self.x = 1
            self.__dict__ = self.__shared_state
    b = Borg()
    b_1 = Borg()
    b.x = 4
    print("Borg Object 'b'", b)  # b and b_1 are distinct objects
    print("Borg Object 'b_1'", b_1)
    print("Object state 'b'", b.__dict__)  # b and b_1 share same state
    print("Object state 'b_1'", b_1.__dict__)
      Borg Object 'b' <__main__.Borg object at 0x0000012FBB4B8F28>
      Borg Object 'b_1' <__main__.Borg object at 0x0000012FBB4959B0>
      Object state 'b' {'1': '2', 'x': 4}
      Object state 'b_1' {'1': '2', 'x': 4}
    # 使用 __new__ 方法本身实现
    class Borg(object):
        __shared_state = {}
        def __new__(cls, *args, **kwargs):
            obj = super().__new__(cls, *args, **kwargs)
            obj.__dict__ = cls.__shared_state
            return obj
    b = Borg()
    b_1 = Borg()
    print("Borg object 'b'", b)
    print("Borg object 'b_1'", b_1)
    print("Object state 'b'", b.__dict__)
    b.x = 1
    print("Object state 'b'", b.__dict__)
    print("Object state 'b_1'", b_1.__dict__)
      Borg object 'b' <__main__.Borg object at 0x0000012FBA0AA8D0>
      Borg object 'b_1' <__main__.Borg object at 0x0000012FBA0AAA20>
      Object state 'b' {}
      Object state 'b' {'x': 1}
      Object state 'b_1' {'x': 1}

5、单例和元类

  • 元类是一个类的类,意味着 该类是它元类的实例
    class MyInt(type):
        def __call__(self, *args, **kwargs):
            print("**** Here's MY int *****", args)
            print("Now do whatever you want with these objects...")
            return type.__call__(cls, *args, **kwargs)
    class int(metaclass=MyInt):
        def __init__(self, x, y):
            self.x = x
            self.y = y
    i = int(2,3)
      **** Here's MY int ***** (2, 3)
      Now do whatever you want with these objects...
    对于已经存在的类,需要创建对象时,将调用 python 的特殊方法 __call__。 由于元类对类创建和对象实例化有更多的控制权,所以可以用于创建单例。 为了控制类的创建和初始化,元类将覆盖 __new____init__ 方法
    class Singleton(type):
        def __init__(self, *args, **kwargs):
            self.__instance = None
            super().__init__(*args, **kwargs)
        def __call__(self, *args, **kwargs):
            if self.__instance is None:
                self.__instance = super().__call__(*args, **kwargs)
                return self.__instance
            else:
                return self.__instance
    class Spam(metaclass=Singleton):
        def __init__(self):
            print("Creating Spam")
    a = Spam()  # class initialized,and create object
    b = Spam()  # class already initialized
    a is b
    c = Spam()
    a is c
    c
    b
    a
      Creating Spam
      True
      True
      <__main__.Spam object at 0x000002B0213E5898>
      <__main__.Spam object at 0x000002B0213E5898>
      <__main__.Spam object at 0x000002B0213E5898>
    其他参考:

使用案例

1、 数据库

  • 对数据库进行多种读取和写入操作

2、 为基础设施运行状况提供监控

  • 通过创建元类,并重写 __init____call__ 方法,同时使用新类中的继承使用方式,这使得元类的创建更加简洁

    class MetaSingleton(type):
         def __init__(self, *args, **kwargs):
            self._instance = None
            super().__init__(*args, **kwargs)
        def __call__(self, *args, **kwargs):
            if self._instance is None:
                self._instance = super().__call__(*args, **kwargs)
            return self._instance
    class HealthCheck(metaclass=MetaSingleton):
        def __init__(self):
            self._servers = []
        def add_server(self):
            self._servers.append("Server 1")
            self._servers.append("Server 2")
            self._servers.append("Server 3")
            self._servers.append("Server 4")
        def change_server(self):
            self._servers.pop()
        self._servers.append("Server 5")
    hc1 = HealthCheck()  # create object hc1
    hc2 = HealthCheck()  # create object hc2
    print(hc1, hc2)  # the object hc1 is hc2, because the object is same
      (<__main__.HealthCheck object at 0x000002AE60F94160>, <__main__.HealthCheck object at 0x000002AE60F94160>)
    hc1.add_server()  # append some serve into servers
    print("Schedule health check for servers(1)...")
      Schedule health check for servers(1)...
    for i in range(4):
        print("Checking ", hc1._servers[i])
          Checking  Server 1
          Checking  Server 2
          Checking  Server 3
          Checking  Server 4
    hc2.change_server()  # pop the servers
    print("Schdule health check for server (2)...")
      Schdule health check for server (2)...
    for i in range(4):
        print("Checking ", hc2._servers[i])
          Checking  Server 1
          Checking  Server 2
          Checking  Server 3
          Checking  Server 5

单例模式的缺点

因为单例模式,具有全局访问的权限,因此可能会出现如下问题:

  • 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用
  • 可能会对同一个对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用
  • 所有依赖于全局变量的类都会由于一个类的改变紧密耦合为全局数据,从而可能在无意中影响另一个类。

单例模式使用建议

  • 只需要创建一个对象,如线程池、缓存、对话框、注册表设置等。如果为每个应用程序创建多个实例,会导致资源的过度使用。单例模式在这种情况下工作得很好。
  • 单例是一种经过时间考验的成熟方法,能够在不带来太多缺陷的情况下提供全局访问点
  • 当使用全局变量或类的实例化非常耗费资源单最终却没有用到他们的情况下,单例的影响可以忽略不计。

工厂模式

“工厂”表示一个负责创建其他类型对象的类

优点:

  • 松耦合,即对象的创建可以独立于类的实现
  • 客户端无需了解创建对象的类,只需知道需要传递的接口、方法和参数,就能够创建所需类型的对象了。
  • 可以轻松在工厂中添加其他类来创建其他类型的对象
  • 工厂还可以重用现有对象。

Factory 模式有三种变体:

  • 简单工厂模式: 允许接口创建对象,但不会暴露对象的创建逻辑
  • 工厂方法模式: 允许接口创建对象,但使用哪个类来创建对象,则交由子类决定
  • 抽象工厂模式: 抽象工厂是一个能够创建一系列相关的对象而无需指定、公开其他具体类的接口。该模式能够提供其他工厂的对象,在其内部创建其他对象。

简单工厂模式

创建抽象类: 抽象的基类(ABCMeta 是 python 的特殊元类,用来生成类 Abstract)

  • 当元类 metaclassABCMeta 并且类方法中使用了装饰器 abstractmethod 后,此类为抽象类,无法实例化。

    from abc import ABCMeta, abstactmethod
    class Section(metaclass=ABCMeta):
        @abstactmethod
        def describe(self):
            print("Personal Section")
    # Can't instantiate abstract class Section with abstract methods describe

工厂方法模式

  • 定义了一个接口创建对象,工厂本身不负责创建对象,将任务交由子类来完成,即子类决定了要实例化哪些类
  • Factory 方法的创建通过集成而不是通过实例化来完成的
  • 工厂方法使设计更加具有可定制型。可以返回相同的实例或子类,而不是某种类型的对象

优点:

  • 具有更大的灵活性,使得代码更加通用,因为不是单纯的实例化某个类
  • 松耦合的,因为创建对象的代码与使用它的代码是分开的。客户端完全不需要关心要传递那些参数以及需要实例化哪些类。

抽象工厂模式

提供一个接口来创建一系列相关对象,而无需指定具体的类。

  • 确保客户端与对象的创建相互隔离,同时还确保客户端能够使用创建的对象。
  • 客户端只能通过接口访问对象。

工厂方法与抽象工厂方法比较

工厂方法 抽象工厂方法
项客户端开方了创建对象的方法 抽象工厂方法包含一个或多个工厂方法来创建一系列的相关对象
使用继承和子类来决定要创建的对象 使用组合将创建对象的任务委托给其他类
用于创建一个产品 用于创建相关产品的系列

Facade (门面)设计模式

门面在隐藏内部系统复杂性的同时,为客户端提供了一个接口,使其可以非常轻松地访问系统

  • 为子系统中的一组接口提供一个统一的接口,并定义一个高级接口来帮助客户端通过更加简单的方式使用子系统
  • 解决的问题 用单个接口对象来表示复杂的子系统。实际上,并不是封装子系统,而是对底层子系统进行组合
  • 促进了实现多个客户端的解耦

最少知识原则

减少对象之间的交互

  • 对于创建的每个对象,都应该考察与之交互的类的数量,以及交互的方式
  • 避免创建许多彼此紧密耦合的类

代理模式-控制对象的访问

代理通常就是一个介于寻求方和提供方之间的中介系统。代理服务器可以封装请求、保护隐私,并且非常适合在分布式架构中运行。

  • 主要目的为其他对象提供一个代理者或占位符,从而控制对实际对象访问

适用场景:

  • 以更简单的方式表示一个复杂的系统。

  • 提高现有的实际对象的安全性。

  • 为不同服务器上的远程对象提供本地接口。

  • 为消耗大量内存的对象提供了一个轻量级的句柄。

    class Actor(object):
        def __init__(self):
            self.is_busy = False
        def occupied(self):
            self.is_busy = True
            print(type(self).__name__, "is occupied with current movie")
        def available(self):
            print(type(self).__name__, "is free for the movie")
        def get_status(self):
            return self.is_busy
    class Agent(object):
        def __init__(self):
            self.principal = None
        def work(self):
            self.actor = Actor()
            if self.actor.get_status():
                self.actor.occupied()
            else:
                self.actor.available()
    r = Agent()
    r.work()
      Actor is free for the movie
    • 为其他对象提供了一个代理,从而实现了原始对象的访问控制
    • 可以用作一个层或接口,以支持分布式访问
    • 通过增加代理,保护真正的组件不受意外的影响

不同类型的代理

  • 虚拟代理
    • 对象实例化后会占用大量内存的话,可以先用占位符来表示,这就是所谓的虚拟代理。即仅在请求或访问对象时,才会创建实际对象。
  • 远程代理
    • 给位于远程服务器或不同地址空间上的实际对象提供了一个本地表示
  • 保护代理
    • 能够控制 **RealSubject(实际主题)**的敏感对象的访问
  • 智能代理
    • 在访问对象时插入其他操作

代理模式的优点

  • 可以通过缓存笨重的对象或频繁访问的对象来提高应用程序的性能
  • 提供对于真实主题的访问授权。因此,只有提供合适权限的情况下,这个模式才会接收委派
  • 远程代理便于与可用作网络连接和数据库连接的远程服务器进行交互,并可用于监视系统
代理模式 门面模式
为其他对象提供了代理或占位符,以控制对原始对象的控制 为类的大型子系统提供了一个接口
代理对象具有与其目标对象相同的接口,并保存有目标对象的引用 实现了子系统之间的通信和依赖性的最小化
充当客户端和被封装的对象之间的中介 门面对象提供了单一的简单接口

装饰器与代理模式的区别

  • 在运行时装饰器向被装饰的对象添加行为,代理是控制访问对象。代理和真实主题之间的关联是在编译时完成的,而不是动态的。

观察者模式

对象(主题)维护了一个依赖(观察者)列表,以便主题可以使用观察者定义的任何方法通知所有观察者所发生的的变化。

  • 应用中存在一个许多其他服务所依赖的核心服务,那么该核心服务将会变成观察者必须观察/监视其变化的主题。

主要目标:

  • 定义了对象间的一对多的依赖关系,从而使得一个对象中的任何更改都将自动通知给其他依赖对象

  • 封装了主题的核心组件

    class Subject(object):
        def __init__(self):
            self.__observers = []
        def register(self, observer):
            self.__observers.append(observer)
        def notify_all(self, *args, **kwargs):
            for observer in self.__observers:
                observer.notify(self, *args, **kwargs)
    class Observer1(object):
        def __init__(self, subject):
            subject.register(self)
        def notify(self, subject, *args):
            print(type(self).__name__, "::Got",args, 'From', subject)
    class Observer2(object):
        def __init__(self, subject):
            subject.register(self)
        def notify(self, subject, *args):
            print(type(self).__name__, "::Got", args, 'From', subject)
    subject = Subject()
    observer1 = Observer1(subject)
    observer2 = Observer2(subject)
    subject.notify_all('notification')
      Observer1 ::Got ('notification',) From <__main__.Subject object at 0x000002784EFD0CF8>
      Observer2 ::Got ('notification',) From <__main__.Subject object at 0x000002784EFD0CF8>

    具体观察者(ConcreteObserver<observer1、observer2>)通过实现观察者(Observer<Observer1、Observer2>)提供的接口向主题注册自己。 每当状态发生变化时,该主题(Subject)都会使用观察者提供的通知方法(notify)来通告所有具体观察者。

现实世界中的观察者模式

案例:

  • 新闻订阅

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from abc import ABCMeta, abstractmethod
    class NewsPublisher(object):