没有什么 “新瓶装旧酒”, 希望温故而知新

快速回顾, 具体细节不多讲, 可能有理解不对的地方, 请留言指正 Discussions · ClericPy/blog

示例代码: Python

设计模式简介

1 简单工厂 Simple Factory

“工厂” 顾名思义就是负责生产产品(对象)的地方, 简单工厂则主要处理单一产品等级结构的情况由一个工厂类来决定创建哪个 / 哪些对象, 将创建对象的操作集中在一起, 使之与对象的使用解耦对象的创建与使用解耦, 也就是俗称的:** **要什么就造什么现实案例: 宠物店新增宠物

class AnimalFactory(object):
    """动物工厂, 负责产生动物"""

    @staticmethod
    def create_animal(name):
        if name == 'Cat':
            return Cat()
        elif name == 'Dog':
            return Dog()


def main():
    dog = AnimalFactory.create_animal('Dog')
    cat = AnimalFactory.create_animal('Cat')


if __name__ == "__main__":
    main()

2 工厂方法 Factory Method

基类负责抽象一个工厂方法(接口), 而不用关心具体如何实现, 具体实现让子类来负责现实案例: 动物园新增动物

import abc


class AnimalFactory(metaclass=abc.ABCMeta):
    """相当于定义了一个接口, 所有类型的工厂都要继承这个类, 并覆盖抽象方法 create_animal"""

    @abc.abstractmethod
    def create_animal(self):
        # 将工厂方法抽象出来
        pass


class CatFactory(AnimalFactory):

    def create_animal(self):
        # 具体的工厂方法, 用来创建一个猫对象
        return Cat()


class DogFactory(AnimalFactory):

    def create_animal(self):
        # 具体的工厂方法, 用来创建一个狗对象
        return Dog()


def main():
    cat = CatFactory().create_animal()
    dog = DogFactory().create_animal()


if __name__ == "__main__":
    main()

3 抽象工厂 Abstract Factory

在工厂方法的基础上, 产品之间具有超过一层的复杂逻辑关系, 比如多种业务品种 / 功能分类因此可以将它们聚集到同一个地方一起管理, 也就是按照特殊的分类角度将各种工厂方法聚合在一起现实案例: 键盘工厂生产不同品牌的有线键盘和无线键盘

from abc import ABC, abstractmethod


class KeyboardFactory(ABC):
    """抽象键盘工厂, 负责生产有线和无线键盘"""

    @abstractmethod
    def produce_wired_keyboard(self):
        pass

    @abstractmethod
    def produce_wireless_keyboard(self):
        pass


class IKBCFactory(KeyboardFactory):

    def produce_wired_keyboard(self):
        return IKBC_C87()

    def produce_wireless_keyboard(self):
        return IKBC_W200()


def main():
    ikbc = IKBCFactory()
    kb = ikbc.produce_wired_keyboard()
    # 如果需要支持 Flico 品牌键盘, 添加好商品, 然后模仿 IKBCFactory 实现一个具体工厂类即可


if __name__ == "__main__":
    main()

4 建造者 Builder

将对象的创建过程独立出来, 使同样的过程可以创造出不同的表达简而言之, 将一个对象的构造过程单独抽离出来, 可变部分用变量表示

import PySimpleGUI as sg


class WindowBuilder(object):

    def __init__(self):
        self.layout = []

    def build(self, text):
        return self.build1(text).build2(text)

    def build1(self, text):
        self.layout.append([sg.Text(text)])
        return self

    def build2(self, text):
        self.layout.append([sg.Text(text)])
        return self

    def show(self):
        window = sg.Window('Test', layout=self.layout)
        window.Show()


if __name__ == "__main__":
    window = WindowBuilder()
    window.build('Test title').show()
    window.build('Test title 2').show()

  • 这里类的链式调用不是必须的

5 原型 Prototype

对已有对象创建一个完整副本时, 这个副本的修改与原型是相互独立之所以借助 copy 而不是重新初始化一个对象, 大概是为了避开 __init__ 方法的开支所以有的地方也叫 clone 模式

import copy


class Prototype(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b
        print('init')

    def shallow_clone(self):
        return copy.copy(self)

    def deep_clone(self):
        return copy.deepcopy(self)

    def __str__(self):
        return str([self.a, self.b])


if __name__ == "__main__":
    a = Prototype(1, 1)
    b = a.shallow_clone()
    b.a = 2
    print(a, b)

6 适配器 Adapter

通过适配器转换一个对象, 在不改变原始对象的前提下, 令它与所期望的接口兼容

class Computer(object):

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

    def show_name(self):
        return self.name


class Adapter(object):

    def __init__(self, obj, kwargs):
        self.obj = obj
        self.__dict__.update(kwargs)


class MobilePhone(object):

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

    def show_nick(self):
        return self.name


if __name__ == "__main__":
    computer = Computer('PC')
    print(computer.show_name())
    mb = MobilePhone('iPhone')
    computer2 = Adapter(mb, {'show_name': mb.show_nick})
    print(computer2.show_name())

7 装饰器 Decorator

从 Python 语言角度比较容易理解, 就是把函数(或者类)对象外面套上一层函数(或者类), 使其在保留原有功能的基础上, 还可以做一些额外的操作

def wrapper(function):
    """包装函数的前后打印信息"""
    print('before wrapping function')
    function.arg = 'test'
    print('after wrapping function')
    return function


def raw_function(num):
    return num


# 使用 @ 语法表示原函数以后不会被用到, 而如果还会用到原函数, 则使用显式转换
new_function = wrapper(raw_function)
# 经过 wrap 后, 新函数可以保留原有功能, 但也包含了 arg 属性
print(new_function.arg)
print(new_function(1))
# before wrapping function
# after wrapping function
# test
# 1

8 外观 Facade

外观模式有助于隐藏系统的内部复杂性,并通过一个简化的接口向客户端暴露必要的部分客户端只需要操作业务层对象和其抽象, 而不需要关心它的具体实现

class Client(object):

    def __init__(self):
        self.a = StepA()
        self.b = StepB()

    def _start(self):
        pass

    def _prepare(self):
        self.a.prepare_something()
        self.b.prepare_something()

    def start(self):
        self._prepare()
        self._start()


class StepA(object):

    def prepare_something(self):
        pass


class StepB(object):

    def prepare_something(self):
        pass


if __name__ == "__main__":
    c = Client()
    c.start()

9 享元 Flyweight

使用对象池来避免重复对象的创建, 节省创建对象的开销比较类似缓存的原理

import time


class Example(object):
    _a = None

    @property
    def a(self):
        if self._a is None:
            self._a = A()
        return self._a


class A(object):

    def __init__(self):
        time.sleep(3)


if __name__ == "__main__":
    e = Example()
    for _ in range(2):
        start_time = time.time()
        a = e.a
        print(f'cost {time.time()-start_time} seconds.')
# cost 3.0013318061828613 seconds.
# cost 0.0 seconds.

10 代理 Proxy

常见于网络代理模式, 正向代理, 反向代理等, 即通过代理层控制网络请求的收发也用于限权, 即只对用户开放部分需要的权限, 保护敏感对象的访问也用于延迟初始化, 比如只在真正第一次用到的时候, 才做比较耗时的初始化操作还被用于在对象被访问时执行一些额外操作, 可以看作一个装饰器, 如引用计数等

class Base(object):

    def normal_operation(self):
        print(f'{self.user_type} calls normal_operation success')

    def admin_operation(self):
        print(f'{self.user_type} calls admin_operation success')


class Proxy(Base):

    def __init__(self, user_type):
        self.user_type = user_type

    def normal_operation(self):
        return super().normal_operation()

    def admin_operation(self):
        if self.user_type == 'admin':
            return super().admin_operation()
        else:
            raise TypeError('Only admin role can use admin_operation')


if __name__ == "__main__":
    admin = Proxy('admin')
    admin.normal_operation()
    admin.admin_operation()
    normal = Proxy('normal')
    normal.normal_operation()
    normal.admin_operation()
    # admin calls normal_operation success
    # admin calls admin_operation success
    # normal calls normal_operation success
    # ...
    # TypeError: Only admin role can use admin_operation

11 责任链 Chain of Responsibility

对象的一系列操作是链式结构, 比如有一个过滤器链, 入口函数接收一段数据, 然后依次经历每个过滤器, 最后输出过滤后的结果, 或抛出错误责任链的价值在于解耦那些复杂的分支逻辑, 如 if-else 地狱, 以及 switch-case 的多层嵌套

class CashRegister(object):
    """超市收款机, 收到纸币分面额存放"""
    handle_5_yuan_list = []
    handle_1_yuan_list = []
    handle_10_yuan_list = []

    def __init__(self, money):
        self.money = money

    def handle_yuan(self):
        """函数柯里化. 这里也可以考虑链式调用的设计: self.xx().yy().zz()"""
        return self.handle_5_yuan

    def handle_5_yuan(self):
        if self.money == 5:
            self.handle_5_yuan_list.append(self.money)
        return self.handle_1_yuan

    def handle_1_yuan(self):
        if self.money == 1:
            self.handle_1_yuan_list.append(self.money)
        return self.handle_10_yuan

    def handle_10_yuan(self):
        if self.money == 10:
            self.handle_10_yuan_list.append(self.money)
        return [
            self.handle_1_yuan_list, self.handle_5_yuan_list,
            self.handle_10_yuan_list
        ]


if __name__ == "__main__":
    # 柯里化
    print(CashRegister(5).handle_yuan()()()())
    # [[], [5], []]

12 命令 Command

通过将请求封装并记录下来, 以便可以实现撤销、重做、复制、粘贴等操作如下代码所示, 对象封装的是一次操作请求, 而不是一种功能, 只有封装请求, 才能把请求需要的参数记录下来, 方便后续撤销

import os


class RenameFile:

    def __init__(self, src, dest):
        self.src, self.dest = src, dest

    def do(self):
        # equals redo
        os.rename(self.src, self.dest)

    def undo(self):
        os.rename(self.dest, self.src)


if __name__ == "__main__":
    a, b = '111.txt', '222.txt'
    print(os.path.exists(a), os.path.exists(b))
    # True False
    rf = RenameFile(a, b)
    rf.do()
    print(os.path.exists(a), os.path.exists(b))
    # False True
    rf.undo()
    print(os.path.exists(a), os.path.exists(b))
    # True False

13 观察者 Observer

当观察对象发生变化, 所有订阅的观察者都会被通知到观察对象与观察者之间属于发布-订阅关系可以参考 python 内置的 threading.Condition 对象的文档, Condition.notify 就是观察者模式的经典用法

class Publisher:

    def __init__(self):
        self.observers = []

    def add(self, observer):
        if observer not in self.observers:
            self.observers.append(observer)

    def remove(self, observer):
        if observer in self.observers:
            self.observers.remove(observer)

    def notify(self):
        for ob in self.observers:
            ob.notify(self)


class Observer(object):

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

    def notify(self, publisher):
        print('received notify from', publisher)

    def sub(self, publisher):
        publisher.add(self)


if __name__ == "__main__":
    ob = Observer('test')
    pub = Publisher()
    ob.sub(pub)
    pub.notify()
    # received notify from <__main__.Publisher object at 0x000001A2771897F0>

14 状态 State

假定有一个状态机对象, 它在创建以后可能会处于数个不同状态, 并具备在不同状态之间切换的能力一般会在状态转换的时候绑定一个回调(callback)一定程度上解耦了大量复杂的 if-else 和 switch-case 关系, 并把某个状态的所有行为封装到了一起, 达到高内聚低耦合参考有限状态机Python 的 Future 体现了一些状态的理念, 具体参考一些状态搭配的 done_callback

_PENDING = base_futures._PENDING
_CANCELLED = base_futures._CANCELLED
_FINISHED = base_futures._FINISHED

15 策略 Strategy

定义一系列算法, 在运行时可按照对应策略灵活切换, 进而得到想要的结果可以参考 Python 里 list 的 sort 方法的 key 参数

class VIPStrategy(object):

    def calculate(self, origin_price):
        """江湖上俗称的会员打八折, 非会员七五折"""
        return origin_price * 0.8


class NONVIPStrategy(object):

    def calculate(self, origin_price):
        return origin_price * 0.75


class User(object):

    def __init__(self, name, user_type='normal'):
        self.name, self.user_type = name, user_type
        if self.user_type == 'VIP':
            self.pricing_strategy = VIPStrategy()
        else:
            self.pricing_strategy = NONVIPStrategy()

    def pricing(self, origin_price):
        return self.pricing_strategy.calculate(origin_price)


if __name__ == "__main__":
    user = User('Tom', 'normal')
    print(user.pricing(100))
    # 75.0
    user = User('Tom', 'VIP')
    print(user.pricing(100))
    # 80.0

16 模板 Template

提取不变部分, 将可变部分交给扩展实现父类将整个骨架抽象出来, 子类继承后将血肉填充进去, 胖瘦随心体现了"依赖抽象, 而不是实现"的设计原则, 父类知道做什么, 子类知道怎么做

from abc import ABC, abstractmethod


class DrinkBase(ABC):

    def __init__(self):
        self.water = []

    @abstractmethod
    def get_water(self):
        pass

    @abstractmethod
    def drink(self):
        pass


class Drink(DrinkBase):

    def __init__(self):
        super().__init__()

    def get_water(self):
        self.water.append(1)

    def drink(self):
        self.water.pop(0)


if __name__ == "__main__":
    d = Drink()
    print(d.water)
    d.get_water()
    d.get_water()
    print(d.water)
    d.drink()
    print(d.water)
    d.drink()
    print(d.water)

17 混入 Mixin

通过多个零散的功能合并成完整功能的一种继承方式体现了迪米特法则, 每个功能对其他功能了解的越少越好, 越独立越好, 也就是解耦体现了职责单一原则, 将紧耦合的面向对象转为更清晰的面向接口, 即鸭子类型体现了关注点分离原则, 复杂的问题拆分成独立的小问题, 更容易维护~~

class Human(object):

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


class EatMixin(object):

    def eat(self, food):
        pass


class WalkMixin(object):

    def walk(self, destination):
        pass


class Male(Human, EatMixin, WalkMixin):

    def __init__(self, name):
        super().__init__(name)


if __name__ == "__main__":
    male = Male('Tom')
    male.eat('food')
    male.walk('beach')

18 单例 Singleton

保证一个类只产生一个实例对 Python 来说, 重载构造函数 __new__ 来复用已有对象, 以减轻多次实例化时的额外开销之前也见过很多讲 Python 的地方提到, 绝大多数单例模式适用的场景, 可以用 Borg 模式代替, 不过也出现了很多反对 Borg 模式的声音, 这里就不再多提了, 感兴趣的 google 之: https://www.google.com/search?q=Borg+Design+singleton

class Singleton(object):
    conn = None

    def __new__(cls, *args, **kwds):
        if cls.conn is None:
            cls.conn = Object()
        return cls.conn

19 备忘录 Memento

通过一个"备忘录”, 在不破坏封装的前提下, 记录对象状态的变化, 以便在不同状态之间灵活地转换数据库事务管理机制的回滚操作, 就是这种模式

class Browser(object):

    def __init__(self, states):
        self.states = states
        self.index = 0

    def back(self):
        if len(self.states) > self.index > 0:
            self.index -= 1

    def forward(self):
        if len(self.states) > self.index + 1:
            self.index += 1


if __name__ == "__main__":
    b = Browser([1, 2, 3, 4])
    for i in range(3):
        print(b.forward() or b.index, end=' ')
    for i in range(3):
        print(b.back() or b.index, end=' ')
    # 1 2 3 2 1 0 

20 空对象 Null Object

一个空对象可以看作某个数据类型的零值空对象的好处是, 验证返回值的时候, 可以简化** EAFP 和 LBYL 两种防御性编程风格**的复杂逻辑, 返回一个合法空值


class Null(object):
    """Null instance will return self when be called, and it will alway be False."""

    def __init__(self, *args, **kwargs):
        return

    def __call__(self, *args, **kwargs):
        return self

    def __getattr__(self, mname):
        return self

    def __setattr__(self, name, value):
        return self

    def __getitem__(self, key):
        return self

    def __delattr__(self, name):
        return self

    def __repr__(self):
        return ""

    def __str__(self):
        return ""

    def __bool__(self):
        return False

    def __nonzero__(self):
        return False

21 解释器 Interpreter

常用在领域特定语言(Domain Specific Language,DSL)的场景需要构建语法树, 定义终结符与非终结符文法的设计非常考验能力等用到再学也来得及, 不多赘述

概括

三种工厂模式:

  1. 将对象的创建与使用隔离开来
  2. 联想到汽车工厂, 开车的人不需要学会怎样造车
  3. 切忌修改抽象基类, 遵循开闭原则, 可以考虑新建或者扩展. 毕竟依赖于抽象的接口而不是具体实现, 如果接口一变, 所有子类全要修改.

建造者模式:

  1. 将对象的创建过程中, 不变的部分抽离出来, 剩下的用变量来补
  2. 联想到一个车间流水线, 按部就班造产品
  3. 有选择地造所需要的东西, 整个建造流程标准化

原型模式:

  1. clone 模式
  2. 联想到有丝分裂 – 我生我自己
  3. 按自己的需要灵活调整 clone 策略, 深浅自选, 必要时候就算实例化一个新对象也没问题

适配器模式:

  1. 通过一个适配器的包装, 在不改变原有源代码的前提下, 使一个类符合所期望的接口
  2. 联想到插头的转接头, 来适配不同标准的插座; 或者手机数据线的转接头; 或者同时支持多种货币的支付系统
  3. 切忌削足适履, 破坏了原有的基础功能

装饰器模式:

  1. 与适配器模式类似, 都可以被称为 包装(wrapper) 模式, 并都不影响原有功能
  2. 与适配器不完全相同, 适配器的目的是实现接口兼容, 功能并没有变化, 装饰器则是在原有功能基础上增加其他操作
  3. 装饰器模式好用但不能滥用, 就像 Python 里的函数, 外面如果包裹一层装饰器, 尽管可以配合 functools.wrap 保留函数名, 栈深度还是会发生变化, 导致原有的一些结构特性遭到破坏

外观模式:

  1. 业务层与底层实现相隔离, 提供人类友好的客户端
  2. 联想到键盘, 工作原理不需要暴露给用户, 用户只要知道如何按键
  3. 为了避免底层出现破坏完整性的修改, 最好依赖抽象而不是具体的实现

享元模式:

  1. 当有大量重复对象要创建时, 可以直接从已缓存的对象池里直接取, 来节省重新创建对象的开销
  2. 联想到 python 的 -5 到 256, 这个范围里的整数初始化的时候, 不需要重新申请内存
  3. 较倾向缓存思想, 所以缓存失效的相关风险同样要考虑; 此外要确认操作真的是重复对象, 而不会有意外情况

责任链模式:

  1. 解耦复杂的多层嵌套逻辑判断
  2. 联想到了超市收银台机器里一个个不同面额纸币的槽
  3. 责任链基于链式结构, 所以与之相关的一些约束也同样有效, 比如逻辑上的有序和连续值得注意

命令模式:

  1. 最经典的就是撤销(undo)和重做(redo)的场景
  2. 联想到编辑器里的 CTRL-Z 和 CTRL-Y
  3. 撤销和重做功能的实现牵扯到一些一致性问题, 所以要提前设计好相关功能, 有些场景反而可以考虑备忘录模式

观察者模式:

  1. 观察对象如果发生变化, 所有观察者都会知晓并执行相应 callback
  2. 联想到火车站候车, 如果检票口状态从候车中变为正在检票, 所有乘客进入检票环节
  3. 观察者和观察对象之间避免循环依赖
  4. 观察者模式和发布-订阅模式经常被放在一起说
    1. 两者实际上都是在讲发布-订阅关系, 有不少地方对两者的区别概括为"别名”, 也就是两种模式实质上没有区别, 都可以包含以下所有特性
    2. 观察者与观察对象之间是紧耦合同步通知的, 两者直接对话
    3. 发布者和订阅者之间是通过发布渠道和订阅渠道解耦, 中间件的存在使整个通知过程可以在异步模型中实现, 也就是说发布者不用关心订阅者是谁, 只要通知到发布渠道, 由后者再发送到订阅者即可; 同理, 订阅者也不需要直接联系到发布者本尊, 只需要与订阅接口打交道, 做好订阅与退订环节
    4. 发布者和订阅者之间的环节可以放在同一个调度中心, 也可以分别实现一个调度中心
    5. 提到同步通知和异步通知的实现, 很容易就联想到了 RPC 和消息队列两种通信方式

状态模式:

  1. 一个对象具有多种状态, 并可以在这些状态之间转换, 转换时也会产生一定的影响
  2. 联想到了事件循环中, 注册对象各种状态的转换, 会触发绑定的回调函数; 也会想到 Bottle 框架的那些 route 装饰器
  3. 状态的定义要足够典型, 如果原本逻辑关系足够清晰, 状态行为也不复杂, 则没有必要强行拆开条件/分支语句, 犯过度设计的错误

策略模式:

  1. 对象拥有多种可能行为, 具体行为在运行时确定
  2. 联想到购物网站的定价策略和打折策略
  3. 如果是复合型策略, 要注意优先级(权重)的区分
    1. 共享性质的策略, 要有优先级的顺序之分, 比如折上折的情况, 一般是按最大折扣的策略, 先计算大折扣
    2. 排他性质的策略, 要定好精确的权重, 比如某个人既是会员又有打折优惠券, 而会员减免比优惠券的权重高, 所以只执行会员定价策略

模板模式:

  1. 父类决定要做什么, 子类决定怎样去做
  2. 联想到了英语考试的完形填空题; 想到 jinja2 模版库
  3. 如果改动了抽象父类, 可能会导致完整性被破坏, 所以要遵循开闭原则

混入模式:

  1. 尽可能地将功能拆分成职责单一的类来进行继承, 功能间解耦
  2. 联想到了拼积木, 每个积木都是独立微小的存在, 却可以拼成高楼大厦汽车火箭
  3. 分的太零碎, 代码会到处都是, 有时候还不如按合并放到同一个类下的静态方法里去

单例模式:

  1. 确保了一个类只有一个实例化对象
  2. 联想到生活中多次拨打客服电话, 实际每次接电话的都是同一个人
  3. 复用的对象一般不会有影响使用的不好的变化

备忘录模式:

  1. 不破坏封装的前提下保存状态, 并可以在状态之间转换
  2. 联想到象棋游戏里的悔棋; 仙剑奇侠传的 S&L 大法
  3. 存储的状态如果过多, 会额外占用很多资源
  4. 有的历史状态如果回滚, 会导致意想不到的坏事发生, 比如破坏一致性/连续性

空对象模式:

  1. 空即是不色, 色不是空
  2. 联想到 go 语言里的零值
  3. 有时候默认值使用一个自定义的空对象比 None 更好, 因为这样就可以在传入 None 时保留 None 的原意
  4. 空对象的处理要达成共识, 有时候可以自定义一些对应方法在该对象中, 这时候就需要依赖抽象接口来保证一致性

解释器模式:

  1. 需要设计合适的文法, 构建语法树, 定义终结符与非终结符
  2. 联想到五线谱; SQL 语法
  3. 平时非 DSL 情景下, 没必要用到也不要强行去用

其他设计模式:

就不一一列举了, 比如

  1. MVC 模式, 也是关注点分离原则的具体体现
  2. 迭代器模式, 直接就可以联想到链表结构, 也就是每次操作不需要知道整个对象集有多少, 只需要实现 has_next 和 next 接口即可
  3. 中介者模式, 主要就是在多个对象之间提供一个松耦合的通信渠道, 降低多个对象之间通信的复杂性
  4. 桥接模式, 也是讲抽象与实现分离, 遵循的也是依赖抽象而不依赖具体实现的原则
  5. 访问者模式, 简单的说, 就是在一个对象 A 里用一个变量存下另一个对象 B, 从而 A 可以对 B 做一些想做的事情. 但是切忌产生循环依赖的问题

总结

就像某个编程语言的崛起是为了解决问题, 设计模式的出现, 也是对以往特定问题解决思路的一种经典归纳. 因此, 设计模式可以被看作是一种常识性答案, 却不是那种真理性质的答案, 它可能是在某个时期解决问题的最佳选择, 却不一定永远都是最佳, 永远也不要迷信设计模式, 甚至为了用而用. 正如 TDD 之父 Kent Beck 谈到测试驱动开发时的态度一样不要过度测试. 而设计模式也是这个道理, 更何况, 为炫技却影响代码的可读性, 从来都不是一件靠谱的事情.一句话, “软件工程没有银弹".