设计模式在Python中的完美实现

Python设计模式

1.0 前言

得益于Python的鸭子类型设计原则,Python相比于 C++ 和 Java 在实现设计模式上更加灵活,甚至可以轻松的将多种设计模式组合出更加强大的组合模式。

文章不会依照常见书籍的做法实现设计模式,而是编写出更加Pythonic的设计模式。

主要包括如下几个主题:

  • 设计模式——元素
  • 设计模式分类
  • Python模式——创建模式
    • 单例模式
    • 工厂模式
    • 原型模式
    • 建造者模式
  • Python模式——结构化模式
    • 适配器模式
    • 外观模式
    • 代理模式
  • Python模式——行为模式
    • 迭代器模式
    • 观察者模式
    • 状态模式

1.1 设计模式——元素

首先了解下设计模式的通用元素

设计模式是用来解决面向对象系统中一个或一类问题重复设计。

几乎所有的设计模式都有以下元素:

  • 名字(name):描述模式的标题。

    比如观察者模式,一个好的标题可以让人见名知意。

  • 上下文(context):问题出现的情景

    前边说了设计模式是解决一类问题的,既然是问题肯定就会有遇到该问题的情景,就是上下文。

  • 问题(problem):需要解决的问题。

    可以从以下几个方面描述一个问题:

    • 需求(requirement):解决方案要满足的需求。比如,发布者 - 订阅者模式的实现必须支持HTTP。
    • 约束(constraint):解决方案的约束(如果有)。比如,可扩展的对等发布者模式不应该交换三个以上的消息来发布通知。
    • 性质(property):解决方案想要的一些性质。比如,该解决方案要在不同的操作系统通用。
  • 解决方案(solution):问题的实际解决方案。

    包括元素的结构和职责、静态关系以及运行时的交互(协作)

    还包括解决问题的程度,和没有解决的程度,还应当尽量提及它的后果,即应用一个设计模式的结果和权衡

注:一种设计模式几乎不可能完全解决导致问题的所有因素,但却让其中一些因素对相关或替代的实现开放。

1.2 设计模式分类

根据解决问题的类型,设计模式被划分为三种,当然随着语言的发展,以后可能会出现新的解决新的问题的方法,这些方法被重复使用,进而出现新的设计模式种类:

  • 创建模式:主要解决对象创建和初始化相关的问题。
    • 工厂模式:确保以可重复可预测的方式创建相关类的实例。
    • 原型模式:通过复制对象来创建相似的对象。
    • 单例模式:类实例化时只被创建初始化一次。
    • 相关模式:确保一个类的任何实例能够共享相同的初始状态。
  • 结构化模式:将对象往有意义的结构中组合和组装,提供可重复使用的行为。
    • 代理模式:通过上层行为的包装层(wrapper)控制对一个对象及其方法的访问。
    • 组合模式:使用一个类表示一个由许多组件同时组成的对象。
  • 行为模式:解决由对象的运行时交互引起的问题,以及它们如何分配责任的问题。
    • 中介者模式:确保所有对象在运行时使用松耦合方式来相互引用。
    • 观察者模式:当资源的状态发生变化时,对象希望被通知,但是不希望保持轮询资源来发现改变(系统中可能有很多这样的对象实例)。

设计模式为什么被划分为三种?

  • 我们以面向对象的思维思考一下一个系统的生命周期:

    • 初期:系统中的各个对象被初始化创建,这个时候会出现需要一些定制化的创建:比如说重复创建问题,对象共享状态等问题。

      此时就出现了创建模式,解决对象初始化创建的问题。

    • 中期:当各个对象被创建出来后,会出现对象过多或者过于复杂、独立单一等问题,导致难以利用这些对象去构成系统中的组织。

      此时就出现了结构化模式,去解决对象组合和组装,提供系统使用的组织行为的问题。

    • 后期:当有了系统的组织行为后,系统基本上可以运行,但是此时又出现了新的问题:组织行为越来越多,并且行为之间的交互也越来越麻烦。

      此时就出现了行为模式,解决对象的运行时交互引起的问题,以及它们如何分配责任的问题

可以发现创建模式、结构化模式和行为模式的顺序隐式地嵌入在一个系统运行时对象的生命周期中。对象首先被创建,然后被组合成有用的结构,再然后它们进行交互。

1.3 Python模式——创建模式

单例模式

要求

  • 一个类必须只有一个实例,该实例可以通过一个公开的访问点访问。
  • 该类在继承扩展后任然符合单例模式。

实现

  1. 重写Python的魔法方法__new__可以实现单例模式:
def test_single(cls):
    """测试是否为单例类"""
    return cls() == cls()

class Singleton:
    """单例类"""
    _instance = None

    def __new__(cls):
        if cls._instance == None:
            cls._instance = object.__new__(cls)
        return cls._instance

if __name__ == "__main__":
    print(test_single(Singleton)) # true

继承Singleton类的类也符合单例模式:

class SingletonA(Singleton):
    """继承单例类的类也是单例类"""
    pass

if __name__ == "__main__":
    print(test_single(SingletonA))	# true
  1. 定义一个单例元类,重写该类的__call__方法,也可以实现单例类。
class MetaSingleton(type):
    """单例元类, 继承该类的所有类都是单例类"""
    def __init__(cls, *args):
        print(cls, "__init__ method called with args", args)
        type.__init__(cls, *args)
        cls.instance = None
    
    def __call__(cls, *args, **kwargs):
        if not cls.instance:
            print(cls, "creating instance", args, kwargs)
        return cls.instance


class SingletonM(metaclass=MetaSingleton):
    """继承单例元类的顶级单例类,后续继承该类的类都是单例类"""
    pass


if __name__ == "__main__":
    print(test_single(SingletonM))

共享初始状态

类必须为所有实例提供一种共享相同初始状态的方法。确保在特定内存位置只有一个实例存在的技术只是实现此目的的一种方法。

抛开教科书式地设计模式,可以使用更加pythonic的方式实现共享。

class Borg:
    """非单例实现初始状态共享"""
    __shared_state = {}

    def __init__(self):
        self.__dict__ = self.__shared_state

class IBorg(Borg):
    """共享Borg类初始状态"""

    def __init__(self):
        Borg.__init__(self)
        self.state = 'init'

    def __str__(self):
        return self.state
>>> i1 = IBorg()
>>> i2 = IBorg()
>>> print(i1) 
init
>>> print(i2) 
init
>>> print(i1.name) 
zzy
>>> print(i2.name) 
zzy
>>> print(i1==i2) 
False
>>>

Borg共享类与单例类比较

  • 先看下单例类在继承后共享状态情况,可以发现在继承的兄弟类中并没有共享状态:

    >>> class SingletonA(Singleton): pass
    ... 
    >>> class SingletonB(Singleton): pass 
    ... 
    >>> class SingletonA1(SingletonA): pass 
    ... 
    >>> a = SingletonA()
    >>> a1 = SingletonA1() 
    >>> b = SingletonB()   
    >>> a.x = 100
    >>> print(a.x) 
    100
    >>> print(a1.x) 
    100
    >>> print(b.x)  
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'SingletonB' object has no attribute 'x'
    
  • 看下共享类Borg的共享状态情况,可以发现继承Borg共享类的所有子类都共享了Borg类的状态:

    >>> class ABorg(Borg): pass
    ... 
    >>> class BBorg(Borg): pass 
    ... 
    >>> class A1Borg(Borg): pass 
    ... 
    >>> a = ABorg()
    >>> b = BBorg() 
    >>> a1 = A1Borg() 
    >>> a.x = 100
    >>> print(a1.x) 
    100 
    >>> print(a.x)  
    100 
    >>> print(b.x) 
    100 
    

工厂模式

要求

  • 通过类的入口点将不同的参数传递给工厂方法,返回不同的实例对象。

实现

  1. 定义一个员工的抽象基类:初始化员工的信息,定义一个定义角色的抽象基类方法。
  2. 定义不同的角色类继承抽象基类,重写抽象基类方法定义角色。
  3. 定义产生员工的工厂类:定义一个工厂方法接受参数,返回不同的员工实例对象。
from abc import ABCMeta, abstractclassmethod

class Employee(metaclass=ABCMeta):
    """员工抽象基类"""

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    @abstractclassmethod
    def get_role(self):
        pass

    def __str__(self):
        return f"{self.__class__.__name__} - {self.name}, {self.age} years old {self.gender}"
    

class Engineer(Employee):
    """继承抽象基类,重写抽象方法: 定义员工"""

    def get_role(self):
        return "engineering"


class Accountant(Employee):
    """继承抽象基类,重写抽象方法: 定义员工"""

    def get_role(self):
        return "accountant"


class Admin(Employee):
    """继承抽象基类,重写抽象方法: 定义员工"""

    def get_role(self):
        return "administration"


class EmployeeFactory:
    """定义员工工厂类"""

    @classmethod
    def create(cls, name, *args):
        """生成员工实例的工厂方法"""
        name = name.lower().strip()

        if name == 'engineer':
            return Engineer(*args)
        elif name == 'accountant':
            return Accountant(*args)
        elif name == 'admin':
            return Admin(*args)
>>> factory = EmployeeFactory()

>>> print(factory.create('engineer', 'Sam', 25, 'M'))
Engineer - Sam, 25 years old M

>>> print(factory.create('accountant', 'ZZY', 22, 'M')) 
Accountant - ZZY, 22 years old M

>>> admin = factory.create('admin', 'Superli', 32, 'F')
>>> print(admin)
Admin - Superli, 32 years old F

>>> admin.get_role()
'administration'

# 因为工厂方法定义为了类方法,我们可以无需实例化工厂类就产生员工
>>> print(EmployeeFactory.create('admin', 'ZZY', 32, 'M')) 
Admin - ZZY, 32 years old M

抽象基类中的抽象方法一般不写内容,但是继承该类的子类必须重写抽象方法实现具体内容。将工厂类保留的接口方法定义为类方法,可以在直接通过工厂类去调用该方法而无需去实例化化一个工厂类对象。这在有时候可能会比较方便。

原型模式

要求

  • 以一个类的实例作为模板实例,通过复制或克隆此原型来创建新的实例。

实现

  1. 使用深度复制实现

    import copy
    
    class Prototype(object):
        """
        原型基类
        """
        def clone(self):
            return copy.deepcopy(self)
    
    class Register(Prototype):
        """
        一个学生注册类
        """
        def __init__(self, names=[]):
            self.names = names
    
    >>> r1 = Register(names=['bobo', 'jack', 'rose']) 
    >>> r2 = r1.clone()
    >>> print(r1) 
    <__main__.Register object at 0x000001B2F3BA9198>
    >>> print(r2) 
    <__main__.Register object at 0x000001B2F3BA92B0>
    >>> r2.__class__
    <class '__main__.Register'>
    >>> r2.names.append('zzy') 
    >>> r2.names
    ['bobo', 'jack', 'rose', 'zzy']
    >>> r1.names 
    ['bobo', 'jack', 'rose']
    
  2. 使用元类的原型

    import copy
    
    class MetaPrototype(type):
           """
           实现原型模式的元类
           """
           def __init__(cls, *args):
               type.__init__(cls, *args)
               cls.clone = lambda self: copy.deepcopy(self)
    
    
       class PrototypeM(metaclass=MetaPrototype):
           """
           继承原型元类的原型模式基类
           """
           pass
    
    
       class ItemCollection(PrototypeM):
           """
           一个列表容器原型类
           """
           def __init__(self, items=[]):
               self.items = items
    
    i1 = ItemCollection(items=['apples', 'bananas', 'oranges'])
    i2 = i1.clone()
    i1 
    <__main__.ItemCollection object at 0x0000018D57207240>
    i2
    <__main__.ItemCollection object at 0x0000018D57207390>
    i1.items is i2.items 
    false
    
  3. 使用元类来合成模式

    使用元类可以将看似冲突的设计模式合成为新的设计模式。现在使用元类将单例模式和原型模式合并,使其既有单例模式的特性,又有原型模式的特性。

    import copy
    
    class MetaSingletonPrototype(type):
        """单例和原型合并的元类"""
    
        def __init__(cls, *args):
            print(cls, "__init__ method called with args", args)
            type.__init__(cls, *args)
            cls.instance = None
            cls.clone = lambda self: copy.deepcopy(cls.instance)
    
        def __call__(cls, *args, **kwargs):
            if not cls.instance:
                print(cls, "creating prototypeical instance", args, kwargs)
                cls.instance = type.__call__(cls, *args, **kwargs)
            return cls.instance
    
    
    class PrototypeM(metaclass=MetaSingletonPrototype):
        pass
    
    class ItemCollection(PrototypeM):
        def __init__(self, items=[]):
            self.items = items 
    
    >>> i1 = ItemCollection(items=['apples', 'bananas', 'oranges']) 
    <class '__main__.ItemCollection'> creating prototypeical instance () {'items': ['apples', 'bananas', 'oranges']}
    
    >>> i2 = i1.clone()
    
    >>> i1
    <__main__.ItemCollection object at 0x0000021151AA9400>
    >>> i2
    <__main__.ItemCollection object at 0x0000021151AA94A8>
    
    >>> i2.items.append('zzy') 
    >>> i1.items
    ['apples', 'bananas', 'oranges']
    
    >>> i2.items 
    ['apples', 'bananas', 'oranges', 'zzy']
    
    >>> i1.items is i2.items
    False
    
    >>> i3 = ItemCollection(items=['a', 'b', 'c'])
    >>> i3 is i1
    True
    
  4. 原型工厂

    • 将相关模式与工厂模式结合,创建具有共享初始状态的对象。
    • 使用原型模式来实现克隆
    • 结合上边实现一个注册表的功能:注册后的实例对象可以克隆
    import copy
    
    
    class Sprototype:
        """原型类基类"""
        def clone(self):
            """返回深度复制的self"""
            return copy.deepcopy(self)
    
    class Borg:
        """共享初始状态基类"""
        _shared_state = {}
        def __init__(self):
            self.__dict__ = self._shared_state
    
    class PrototypeFactory(Borg):
        """拥有共享初始状态的工厂注册类"""
    
        def __init__(self):
            self._registry = {}
    
        def registry(self, instance):
            """注册给定实例"""
            self._registry[instance.__class__] = instance
    
        def clone(self, klass):
            """返回给定注册后的类的克隆后的实例"""
            instance = self._registry.get(klass)
            if instance == None:
                print('Error:', klass, 'not registered')
            else:
                return instance.clone()
    
    
    class Name(Sprototype):
        """名称原型类"""
        def __init__(self, first, second):
            self.first = first
            self.second = second    
    
        def __str__(self):
            return ' '.join((self.first, self.second))
        
    class Animal(Sprototype):
        """动物原型类"""
        def __init__(self, name, a_type='Wild'):
            self.name = name
            self.type = a_type
    
        def __str__(self):
            return ' '.join((str(self.type), self.name))
    
    >>> name = Name('Bill', 'Bryson') 
    >>> animal = Animal('Elephant') 
    >>> print(name) 
    Bill Bryson
    >>> print(animal) 
    Wild Elephant
    
    >>> # 实例化原型工厂对象
    ... 
    >>> factory = PrototypeFactory()
    
    # 注册对象
    >>> factory.registry(animal) 
    >>> factory.registry(name)   
    
    >>> # 注册完毕实例对象后,克隆任意数量的实例
    ... 
    >>> factory.clone(Name) 
    <__main__.Name object at 0x000001504B6F9908>
    >>> factory.clone(Animal) 
    <__main__.Animal object at 0x000001504B6F9978>
    
    >>> # 克隆没有注册的实例会提示未注册
    ... 
    >>> class C: pass
    ... 
    >>> factory.clone(C)
    Error: <class '__main__.C'> not registered
    >>>
    

建造者模式

要求

  • 将对象的构造和组装分离,确保可以使用相同的构建过程构建不同的表现形式。

实现

  • 使用建造者模式来建造拥有不同房间数量和门廊数量等配置的房屋

  • 通过封装原始的建造者类,可以更加个性化的、方便的定制不同配置的建造者类

    
    
    class Room:
        """房间类:窗户和门"""
        def __init__(self, nwindows=2, doors=1, direction='S'):
            self.nwindows = nwindows
            self.doors = doors
            self.direction = direction
    
        def __str__(self):
            return "Room <facing:%s, windows=#%d>" % (self.direction,
                                                      self.nwindows)
    
    
    class Porch:
        """门廊类:门"""
        def __init__(self, ndoors=2, direction='W'):
            self.ndoors = ndoors
            self.direction = direction
    
        def __str__(self):
            return "Porch <facing:%s, doors=#%d>" % (self.direction,
                                                    self.ndoors)
        
    
    class LegoHouse:
        """ 搭建的整个房屋类:房间和门廊 """
        def __init__(self, nrooms=0, nwindows=0, nporches=0):
            self.nwindows = nwindows
            self.nporches = nporches
            self.nrooms = nrooms
            self.rooms = []
            self.porches = []
    
        def __str__(self):
            msg="LegoHouse<rooms=#%d, porches=#%d>" %(
                self.nrooms,
                self.nporches
            )
    
            for i in self.rooms:
                msg += str(i)
    
            for i in self.porches:
                msg += str(i)
    
            return msg
    
        def add_room(self, room):
            """ 给整件房屋增加一个房间 """
            self.rooms.append(room)
    
        def add_porch(self, porch):
            """ 给整件房屋增加一个门廊 """
            self.porches.append(porch)
    
    
    class LegoHouseBuilder:
        """ 房屋建造者 """
        def __init__(self, *args, **kwargs):
            self.house = LegoHouse(*args, **kwargs)
    
        def build(self):
            """ 构造一个房屋实例并返回 """
            self.build_rooms()
            self.build_porches()
            return self.house
    
        def build_rooms(self):
            """ 构建房间方法 """
            for i in range(self.house.nrooms):
                room = Room(self.house.nwindows)
                self.house.add_room(room)
        
        def build_porches(self):
            """ 构建门廊方法 """
            for i in range(self.house.nporches):
                porch = Porch(1)
                self.house.add_porch(porch)
    
    if __name__ == "__main__":
        builder = LegoHouseBuilder(nrooms=2, nporches=1, nwindows=1)
        print(builder.build())
    
        class SmallLegoHouseBuilder(LegoHouseBuilder):
            """ 将常用的建造者配置封装为子类 """
            def __init__(self):
                self.house = LegoHouse(nrooms=2, nporches=1, nwindows=2)
    
        # 使用封装的建造者配置使用起来更加方便
        small_house = SmallLegoHouseBuilder().build()
        print(small_house)
    
        # 封装一个建造面向北方的房屋类
        class NorthFacingHouseBuilder(LegoHouseBuilder):
            """ 面向北方的房屋建造者类 """
            def build_rooms(self):
                for i in range(self.house.nrooms):
                    room = Room(self.house.nwindows, direction='N')
                    self.house.add_room(room)
            
            def build_porches(self):
                for i in range(self.house.nporches):
                    porch = Porch(1, direction='N')
                    self.house.add_porch(porch)
    
        print(NorthFacingHouseBuilder(nrooms=2, nporches=1, nwindows=1).build())
    
    LegoHouse<rooms=#2, porches=#1>Room <facing:S, windows=#1>Room <facing:S, windows=#1>Porch <facing:W, doors=#1>
    LegoHouse<rooms=#2, porches=#1>Room <facing:S, windows=#2>Room <facing:S, windows=#2>Porch <facing:W, doors=#1>
    LegoHouse<rooms=#2, porches=#1>Room <facing:N, windows=#1>Room <facing:N, windows=#1>Porch <facing:N, doors=#1>
    
  • 注意

    • 建造者模式中具体的执行动作(如:add_room, add_porch)应该放在需要构造的对象的类中(LegoHouse),而不是放在建造者类中(LegoHouseBuilder)。

      建造者类(LegoHouseBuilder)负责的是对执行动作的调用(build_rooms、build_porches)和组装(build)。

      所以一般建造者类在初始化方法中就会获取构造对象(self.house = LegoHouse(*args, **kwargs)),然后在调用和组装的过程中使用该对象调用具体的执行动作。

    • 建造者模式与工厂模式的区别

      两种模式有相似的地方:都是传递不同的入参最终生成不同的对象。

      但是两者有本质上的区别:

      1. 工厂模式重点强调的是入参的不同创造不同的对象,也就是说无中生有,强调创造。
      2. 建造者模式重点强调的是执行动作的不同组合出不同的对象,重点强调组合。

1.4 Python模式——结构化模式

适配器模式

需求

  • 将现有接口包装或调整为另一个期望的接口。

实现

  • 将一个多边形类适配为不同形状的类,并提供预期的接口。

  • 有两种实现方式,一种是通过继承基类,实现预期接口方法的方式。一种是通过在实例化基类对象,通过对象组合的方式,即对象适配器。

  • 继承基类的方式

    多边形基类:

    class Polygon:
        """ 多边形基类 """
        def __init__(self, *sides):
            """ 边的数量 """
            self.sides = sides
    
        def perimeter(self):
            """ 周长 """
            return sum(self.sides)
    
        def is_valid(self):
            """ 判断是否合法 """
            raise NotImplementedError
    
        def is_regular(self):
            """ 判断是否为规则多变形 """
            side = self.sides[0]
            return all([x==side for x in self.sides[1:]])
    
        def area(self):
            """ 计算并返回面积 """
            raise NotImplementedError
    

    使用适配器模式实现三角形类,实现预期接口为is_equilateral,is_isosceles

    class Triangle(Polygon):
        """ 使用适配器模式实现三角形类 """
        def is_equilateral(self):
            """ 判断是否等边 """
            if self.is_valid():
                return super(Triangle, self).is_regular()
        
        def is_isosceles(self):
            """ 判断是否为等腰三角形 """
            if self.is_valid():
                for a,b in itertools.combinations(self.sides, 2):
                    if a == b:
                        return True
            return False
    
        def area(self):
            """ 计算面积 """
            p = self.perimeter() / 2.0
            total = p
            for side in self.sides:
                total *= abs(p-side)
            return pow(total, 0.5)
    
        def is_valid(self):
            """ 判断是否合法 """
            # 两边之和大于第三边
            perimeter = self.perimeter()
            for side in self.sides:
                sum_two = perimeter - side
                if sum_two <= side:
                    raise InvalidePolygonError(str(self.__class__) + "is invalid")
            return True
    
    >>> t1 = Triangle(20, 20, 20)
    >>> t1.is_valid()
    True
    >>> t1.is_equilateral() 
    True
    >>> t1.area()
    173.20508075688772
    
    >>> # 创建一个不合法的三角形 
    ... 
    >>> t2 = Triangle(10, 20, 30) 
    >>> t2.is_valid()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 28, in is_valid
    __main__.InvalidePolygonError: <class '__main__.Triangle'>is invalid
    

    使用适配器模式实现矩形类,预期接口为is_square

    class Rectangle(Polygon):
        """ 使用适配器模式实现矩形 """
        def is_square(self):
            """ 判断是否为正方形 """
            if self.is_valid():
                return self.is_regular()
        
        def is_valid(self):
            """ 判断是否合法 """
            # 4条边
            if len(self.sides) != 4:
                return False
            # 对边相等
            for a,b in [(0,2), (1,3)]:
                if self.sides[a] != self.sides[b]:
                    return False
            return True
    
        def area(self):
            """ 计算面积 """
            if self.is_valid():
                return self.sides[0]*self.sides[1]
    
    >>> r1 = Rectangle(10, 20, 10, 20) 
    >>> r1.is_valid()
    True
    >>> r1.is_square()
    False
    >>> r1.area()
    200
    >>> # 创建一个正方形
    ... r2 = Rectangle(10, 10, 10, 10) 
    >>> r2.is_square()
    True
    
  • 对象适配器的方式

    class Triangle2:
        """ 对象适配器方式实现三角形类 """
    
        def __init__(self, *sides):
            """ 实例化基类对象 """
            self.polygon = Polygon
            
        def perimeter(self):
            return self.polygon.perimeter
        
        def is_valid(f):
            """ 判断是否为三角形 """
            def inner(self, *args):
                perimeter = self.polygon.perimeter()
                sides = self.polygon.sides
    
                for side in sides:
                    sum_two <= side:
                        raise InvalidePolygonError(str(self.__class__) +
                                                    "is invalid"
                                                    )
                result = f(self, *args)
                return result
            return inner
    
        @is_valid
        def is_equilateral(self):
            """ 判断是否为等边三角形 """
            return self.polygon.is_regular()
    
        @is_valid
        def is_isosceles(self):
            """ 判断是否为等腰三角形 """
            for a,b in itertools.combinations(self.polygon.sides, 2):
                if a == b:
                    return True
            return False
    
        def area(self):
            """ 计算面积 """
            p = self.polygon.perimeter() / 2.0
            total = p
            for side in self.polygon.sides:
                total *= abs(p-side)
            return pow(total, 0.5)
    
    • 对象适配器方式与类适配器方式的主要区别:

      • 对象适配器无需继承想要适配的类,需要实例化适配的类。
      • 对象适配器类中用到的适配的类中的属性和方法都必须显示的通过实例化对象获取。
    • 升级版对象适配方式:重写___getatttr__方法动态获取需要适配的类中的属性。

      class Triangle2:
          """ 对象适配器方式实现三角形类 """
          
          # 如果想要调用的方法与实例对象上的方法内容一样,但是方法名不一样,
          # 可以通过映射的方式来定义两个方法名间的关系
          method_mapper = {'is_equilateral':'is_regular'}
      
          def __init__(self, *sides):
              """ 实例化基类对象 """
              self.polygon = Polygon(*sides)
          
          def is_valid(f):
              """ 判断是否为三角形 """
              def inner(self, *args):
                  perimeter = self.perimeter()
                  sides = self.sides
      
                  for side in sides:
                      sum_two = perimeter - side
                      if sum_two <= side:
                          raise InvalidePolygonError(str(self.__class__) + "is invalid")
                  result = f(self, *args)
                  return result
              return inner
      
          @is_valid
          def is_isosceles(self):
              """ 判断是否为等腰三角形 """
              for a,b in itertools.combinations(self.sides, 2):
                  if a == b:
                      return True
              return False
      
          @is_valid
          def area(self):
              """ 计算面积 """
              p = self.perimeter() / 2.0
              total = p
              for side in self.sides:
                  total *= abs(p-side)
              return pow(total, 0.5)
      
          def ___getatttr__(self, name):
              """
              首先查找在映射字典中是否存在对应的方法名,
              如果存在就拿对应的实例对象的方法名去获取其属性。
              否则就直接从实例对象中查找属性。
              """
              name = self.method_mapper.get(name, name)
              return getattr(self.polygon, name)
      
      >>> t1 = Triangle(2, 2, 2)
      >>> t1.is_equilateral()
      True
      >>> t1.area()
      1.7320508075688772
      >>>
      

外观模式

需求

  • 当系统比较复杂时,无需关心系统内部实现,只需要将对应操作的接口暴露出来即可。

实现

  • 定义汽车系统的各个类,然后使用外观模式去提供启动和停止接口。

    汽车系统类:

    class Engine:
        """ 汽车引擎类 """
    
        def __init__(self, name, bhp, rpm, volume, cylinders=4,
            type='petrol'):
            self.name = name
            self.bhp = bhp
            self.rpm = rpm
            self.volume = volume
            self.cylinders = cylinders
            self.type = type
            
        def start(self):
            """启动引擎
            """
            print('Engine started')
    
        def stop(self):
            """停止引擎
            """
            print('Engine stopped')
    
    class Transmission:
        """变速类
        """
        def __init__(self, gears, torque):
            self.gears = gears
            self.torque = torque
            self.gears_pos = 0
    
        def shift_up(self):
            """ 加速 """
            if self.gears_pos == self.gears:
                print('不能加速了')
            else:
                self.gears_pos += 1
                print('加速至', self.gears_pos)
            
        def shift_down(self):
            """ 减速 """
            if self.gears_pos == -1:
                print('倒挡,不能减速了!')
            else:
                self.gears_pos -= 1
                print('减速至', self.gears_pos)
                
        def shift_reverse(self):
            """ 倒挡 """
            self.gears_pos = -1
            print('倒挡!')
            
        def shift_to(self, gear):
            """ 跳档 """
            self.gears_pos = gear
            print(f'调制{gear}档')
        
    class Brake:
        """ 刹车类 """
        
        def __init__(self, number, type='disc'):
            self.type = type
            self.number = number
            
        def engage(self):
            """ 进行刹车 """
            print('%s %d engage' % (self.__class__.__name__, self.number))
    
        def release(self):
            """ 恢复 """
            print('%s %d released' % (self.__class__.__name__, self.number))
    
    class ParkingBrake(Brake):
        """ 停车 """
        def __init__(self, type='drum'):
            super(ParkingBrake, self).__init__(type=type, number=1)
        
    class Suspension:
        """ 悬停 """
        def __init__(self, load, type='mcpherson'):
            self.type = type
            self.load = load
            
    class Wheel:
        """ 轮胎材料 """
        def __init__(self, material, diameter, pitch):
            self.material = material
            self.diameter = diameter
            self.pitch = pitch
            
    class WheelAssembly:
        """ 车轮 """
        def __init__(self, brake, suspension):
            self.brake = brake
            self.suspension = suspension
            self.wheels = Wheel('alloy', 'M12', 1.25)
    
        def aaply_brakes(self):
            """ 踩刹车 """
            print('踩刹车')
            self.brake.engage()
            
    class Frame:
        """ 外壳 """
        def __init__(self, length, width):
            self.length = length
            self.width = width
    

    组装汽车类,并提供启动和停止的接口:

    class Car:
        """ 汽车类——外观模式 """
        def __init__(self, model, manufacturer):
            self.engine = Engine('K-series',85,5000, 1.3)
            self.frame = Frame(385, 170)
            self.wheel_assembiles = []
            for i in range(4):
                self.wheel_assembiles.append(WheelAssembly(Brake(i+1), Suspension(1000)))
            self.transmission = Transmission(5, 115)
            self.model = model
            self.manufacturer = manufacturer
            self.park_brake = ParkingBrake()
            
            self.ignition = False
            
        def start(self):
            """ 启动 """
            print('启动汽车~')
            self.ignition = True
            self.park_brake.release()
            self.engine.start()
            self.transmission.shift_up()
            print('汽车启动了。。')
            
        def stop(self):
            """ 停止汽车 """
            print('停车~')
            for wheel_a in self.wheel_assembiles:
                wheel_a.apply_brakes()
                
            # 调制2挡然后1挡
            self.transmission.shift_to(2)
            self.transmission.shift_to(1)
            self.engine.stop()
            self.transmission.shift_to(0)
            self.park_brake.engage()
            print('停车完毕..')
    

    运行:

    >>> car = Car('Swift', 'Suzuki')          
    >>> car.start()
    启动汽车~
    ParkingBrake 1 released
    Engine started
    加速至 1
    汽车启动了。。
    >>> car.stop()
    停车~
    踩刹车        
    Brake 1 engage
    踩刹车        
    Brake 2 engage
    踩刹车
    Brake 3 engage
    踩刹车
    Brake 4 engage
    调制2档
    调制1档
    Engine stopped
    调制0档
    ParkingBrake 1 engage
    停车完毕..
    

代理模式

需求

  • 包装另一个对象来控制对此对象的访问

实现

  • 记数代理:记录前边工厂模式产生的实例对象数量。

  • 这里重用了Employee类。

    from abc import ABCMeta, abstractclassmethod
    
    class Employee(metaclass=ABCMeta):
        """员工抽象基类"""
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        @abstractclassmethod
        def get_role(self):
            pass
    
        def __str__(self):
            return f"{self.__class__.__name__} - {self.name}, {self.age} years old {self.gender}"
        
    
    class Engineer(Employee):
        """继承抽象基类,重写抽象方法: 定义员工"""
    
        def get_role(self):
            return "engineering"
    
    
    class Accountant(Employee):
        """继承抽象基类,重写抽象方法: 定义员工"""
    
        def get_role(self):
            return "accountant"
    
    
    class Admin(Employee):
        """继承抽象基类,重写抽象方法: 定义员工"""
    
        def get_role(self):
            return "administration"
    

    创建代理工厂类:这里使用的是对象代理,重载___getatttr__方法重定向访问权,获得原实例对象中的属性。

    class EmployeeProxy:
        """ 员工计数代理类 """
        count = 0
    
        def __new__(cls, *args):
            """ 重写构造方法,记录员工数量 """
            instance = object.__new__(cls)
            cls.incr_count()
            return instance
    
        def __init__(self, employee):
            self.employee = employee
    
        @classmethod
        def incr_count(cls):
            """ 增加员工数量 """
            cls.count += 1
    
        @classmethod
        def decr_count(cls):
            """ 减少员工数量 """
            cls.count -= 1
        
        @classmethod
        def get_count(cls):
            """ 得到员工数量"""
            return cls.count
    
        def __str__(self):
            return str(self.employee)
    
        def __getattr__(self, name):
            """ 重定向获取员工实例对象属性 """
            return getattr(self.employee, name)
    
        def __del__(self):
            self.decr_count()
    
    
    
    class EmployeeProxyFactory:
        """ 返回计数代理对象的员工工厂类 """
    
        @classmethod
        def create(cls, name, *args):
            """ 生产员工的工厂类 """
            name = name.lower().strip()
    
            if name == 'engineer':
                return EmployeeProxy(Engineer(*args))
            elif name == 'accountant':
                return EmployeeProxy(Accountant(*args))
            elif name == 'admin':
                return EmployeeProxy(Admin(*args))
    
    >>> factory= EmployeeProxyFactory()  
    >>> engineer = factory. create ('engineer','Sam', 25,'M' )
    >>> EmployeeProxy.get_count()  
    1
    >>> admin = factory. create ('admin','Sam', 25,'M' )       
    >>> EmployeeProxy.get_count()
    2
    >>> accountant = factory. create ('accountant','Sam', 25,'M' ) 
    >>> EmployeeProxy.get_count()
    3
    >>> del admin
    >>> EmployeeProxy.get_count()
    2
    >>> del accountant
    >>> EmployeeProxy.get_count()
    1
    
  • Python的弱引用模块提供了实现代理对象的方法:

    >>> import weakref
    >>> import gc
    >>> engineer=Engineer('zzy', 25, 'M') 
    >>> # 查看对象的引用计数
    ... len(gc.get_referrers(engineer))  
    1
    >>> # 现在创建一个对它的弱引用
    ... engineer_proxy = weakref.proxy(engineer) 
    >>> # 若引用具有和代理对象一样的属性
    ... print(engineer_proxy) 
    Engineer - zzy, 25 years old M
    >>> engineer_proxy.get_role()       
    'engineering'
    >>> # 查看代理对象的引用计数并没有增加
    ... len(gc.get_referrers(engineer)) 
    1
    >>>
    

1.5 Python模式——行为模式

行为模式是系统生命周期最后阶段的模式,封装了对象之间的通信和交互,该模式往往倾向于对象组合。

迭代器模式

需求

  • 提供一种连续访问容器对象中的元素而不会暴露底层对象本身的方法。

实现

  • Python中的迭代器必须实现__iter__方法,并且该对象还要能响应返回迭代器实例的iter函数。

  • 实现一个质数迭代器:

    
    class Prime:
        """ 一个质数迭代器 """
    
        def __init__(self, initial, final=0):
            self.current = initial
            # 迭代终止位,如果不传,则会无限迭代
            self.final = final
    
        def __iter__(self):
            return self
    
        def __next__(self):
            return self._compute()
    
        def _compute(self):
            """ 计算下一个质数 """
            num = self.current
    
            while True:
                is_prime = True
                for x in range(2, int(pow(self.current, 0.5)+1)):
                    if self.current %x == 0:
                        is_prime = False
                        break
    
                num = self.current
                self.current += 1
    
                if is_prime:
                    return num
    				
                if self.final > 0 and self.current > self.final:
                    raise StopIteration
    
    >>> p = Prime(2, 10)
    >>> for num in p:
    ...     print(num) 
    ... 
    2
    3
    5
    7
    

    不传入标志位,结合itertools模块的方法可以达到多种结果:

    • 计算前100个质数:

      >>> p = Prime(2)
      >>> import itertools
      >>> list(itertools.islice(Prime(2), 100)) 
      [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 
      307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
      
    • 过滤出个位以1结尾的前10个质数:

      >>> list(itertools.islice(itertools.filterfalse(lambda x: x % 10 != 1, Prime(2)), 10)) 
      [11, 31, 41, 61, 71, 101, 131, 151, 181, 191]
      

观察者模式

需求

  • 解耦对象,允许在同一时刻一组对象(订阅者)跟踪另一组对象(发布者)的改变。避免一对多的依赖和引用,同时保持它们的交互活动。
  • 该模式又叫做发布者—订阅模式。

实现

  • 实现一个Alam类,该类在自己的线程中运行,并且每隔1s生成定期报警。它也可以作为发布者类,当发生报警时通知其订阅者。

    import threading
    import time
    import pdb
    
    from datetime import datetime
    
    class Alarm(threading.Thread):
        """ 发布者:定期通知类 """
    
        def __init__(self, duration=1):
            self.duration = duration
            self.subscribers = []
            self.flag = True
            threading.Thread.__init__(self)
    
        def register(self, subscriber):
            """ 为警告通知注册一个订阅者 """
            self.subscribers.append(subscriber)
    
        def notify(self):
            """ 通知所有订阅者 """
            for subscriber in self.subscribers:
                subscriber.update(self.duration)
    
        def stop(self):
            """ 停止线程 """
            print('线程终止')
            self.flag = False
    
        def run(self):
            """ 运行警告通知 """
            while self.flag:
                time.sleep(self.duration)
                self.notify()
    
    
    class DumbClock:
        """ 订阅者:订阅Alarm对象并接收通知,同时利用通知更新它的时间 """
    
        def __init__(self):
            self.current = time.time()
        
        def update(self, *args):
            """ 发布者回调方法 """
            self.current += args[0]
    
        def __str__(self):
            return datetime.fromtimestamp(self.current).strftime('%H:%M:%S')
    
    
    if __name__ == "__main__":
        alarm = Alarm(duration=1)
        clock = DumbClock()
        alarm.register(clock)
        alarm.start()
        print(clock)
        time.sleep(5)
        print(clock)
    
    PS C:\Users\ZZY> & python c:/Users/ZZY/Desktop/a.py
    11:46:39
    11:46:43
    
    • 该模式的核心是要在发布者类中要有注册订阅者的方法,也就是获取订阅者对象的方法,还有一个通知订阅者的方法(该方法调用订阅者中的接受更新的方法),所以在订阅者中要有一个供发布者调用的接口用来接受发布者的消息。

状态模式

需求

  • 将对象的内部状态封装到另一个类(状态对象)。对象通过将内部封装的状态切换到不同的值来更改其自身状态。

实现

  • Python中的对象可以通过__class__类属性获取类中的属性和方法。我们可以通过修改__class__属性达到切换状态的效果。

  • 实现一个可以切换电脑运行状态的案例,该案例使用迭代器的方式去切换状态:

    import random
    
    
    class ComputerState:
        """ 电脑状态基类 """
    
        name = "state"
        next_states = []
        random_states = []
    
        def __init__(self):
            self.index = 0
        
        def __str__(self):
            return self.__class__.__name__
    
        def __iter__(self):
            return self
    
        def change(self):
            return self.__next__()
    
        def set(self, state):
            """
            设置状态:
            如果该状态存在于下个状态列表中,首先会切换至下个状态,
            如果不存在下个状态列表中,就切换至随机状态列表的状态
            """
            if self.index < len(self.next_states):
                if state in self.next_states:
                    self.index = self.next_states.index(state)
                    self.__class__ = eval(state)
                    return self.__class__
                else:
                    current = self.__class__
                    new = eval(state)
                    raise Exception(f'Illegal transition from {current} to {new}')
            else:
                self.index = 0
                if state in self.random_states:
                    self.__class__ = eval(state)
                    return self.__class__
    
        def __next__(self):
            """ 切换至下个状态:
                如果存在下个状态,首先会切换至下个状态,
                如果不存在下个状态,就切换至随机状态
            """
            if self.index < len(self.next_states):
                self.__class__ = eval(self.next_states[self.index])
                self.index += 1
                return self.__class__
            else:
                self.index = 0
                if len(self.random_states):
                    state = random.choice(self.random_states)
                    self.__class__ = eval(state)
                    return self.__class__
                else:
                    raise StopIteration
    
    
    class ComputerOff(ComputerState):
        """
        关机状态:
        关机状态固定下一个状态为开机状态,
        随机状态有挂起,休眠,关机
        """
        next_states = ['ComputerOn']
        random_states= ['ComputerSuspend', 'ComputerHibernate', 'ComputerOff']
    
    class ComputerOn(ComputerState):
        """
        开机状态:
        没有固定的下一个状态,
        随机状态有挂起,休眠,关机
        """
        random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff']  
    
    class ComputerWakeUp(ComputerState):
        """
        运行状态:
        没有固定的下一个状态,
        随机状态有挂起、休眠、关机
        """
        random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff']
    
    class ComputerSuspend(ComputerState):
        """
        挂起状态:
        挂起状态下一个状态为运行状态,
        随机状态有挂起、休眠、关机
        """
        next_states = ['ComputerWakeUp']
        random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff']
    
    class ComputerHibernate(ComputerState):
        """
        休眠状态:
        休眠状态下一个固定状态为开机状态,
        随机状态有挂起、休眠、关机
        """
        next_states = ['ComputerOn']
        random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff']
    
    
    class Computer:
        """ 可以改变状态的电脑类 """
    
        def __init__(self, model):
            self.model = model
            # 默认为关机
            self.state = ComputerOff()
    
        def change(self, state=None):
            """ 修改至指定状态 """
            if state == None:
                return self.state.change()
            else:
                return self.state.set(state)
    
        def __str__(self):
            """ 返回状态 """
            return str(self.state)
            
    

    运行结果:

    >>> c = Computer('HUAWEI') 
    >>> c.change()				
    <class '__main__.ComputerOn'>
    >>> c.change('ComputerOff') 
    <class '__main__.ComputerOff'>
    >>> c.change()
    <class '__main__.ComputerOn'>
    >>> c.change()
    <class '__main__.ComputerSuspend'>
    >>> c.change()
    <class '__main__.ComputerWakeUp'>
    >>> c.change()
    <class '__main__.ComputerOff'>
    

总结

对Python学习了解越深,越能体会人生苦短,我用Python这短短几个字中的含义:

  • 优雅
  • 人性化思维

当我们学会以面向对象的思维去学习设计模式,会发现设计模式并没有想象的复杂:不就是变着法去操作过来操作过去一些对象嘛。

个人觉得学习设计模式更重要的是理解它为什么要这样设计,以及它解决了什么问题。

关于如何实现这点要结合具体的编程语言,但核心点就是对象:如何去获取、改变、组装对象的属性。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值