title: 设计模式
top: 44
date: 2022-01-24 09:46:47
tags:
- 设计模式
- python
categories: - software engineering
软件设计模式的概念与意义
1. 软件设计模式的概念
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
2. 学习设计模式的意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
UML统一建模语言
软件设计七大原则
分别为开闭原则、里氏替换原则、依赖倒置原则、单一职责原则、接口隔离原则、迪米特法则和合成复用原则。 这些原则的目的只有一个:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。
设计原则 | 一句话归纳 | 目的 |
---|---|---|
开闭原则 | 对扩展开放,对修改关闭 | 降低维护带来的新风险 |
依赖倒置原则 | 高层不应该依赖低层,要面向接口编程 | 更利于代码结构的升级扩展 |
单一职责原则 | 一个类只干一件事,实现类要单一 | 便于理解,提高代码的可读性 |
接口隔离原则 | 一个接口只干一件事,接口要精简单一 | 功能解耦,高聚合、低耦合 |
迪米特法则 | 不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度 | 只和朋友交流,不和陌生人说话,减少代码臃肿 |
里氏替换原则 | 不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 |
合成复用原则 | 尽量使用组合或者聚合关系实现代码复用,少使用继承 | 降低代码耦合 |
GoF 的 23 种设计模式的分类和功能
GoF(“四人帮”,指Gamma, Helm, Johnson & Vlissides, Addison-Wesley )
1. **根据目的来分:**根据模式是用来完成什么工作来划分,这种方式可分为创建型模式结构型模行为型模式
1. 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
2. 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
3. 行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
- **根据作用范围来分:**根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式对象模式
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。工厂方法、(类)适配器、模板方法、解释器。
2. 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
创建型模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
单例(Singleton)模式:
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式的应用场景
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
class MyClass(Singleton):
def __init__(self, a):
self.a = a
ms1 = MyClass(1)
ms2 = MyClass(2)
print(ms1.a, ms2.a)
print(id(ms1), id(ms2))
"""
2 2
139843914173312 139843914173312
"""
原型(Prototype)模式:
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
原型模式的优点:
- 原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
简单工厂方法(Simple Factory Method)模式:
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
- 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
模式的结构
工厂方法模式的主要角色如下
- 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
- 具体产品(ConcreteProduct):是简单工厂模式的创建目标。
from abc import ABCMeta, abstractmethod
# 抽象产品角色,以什么样的表现去使用
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 产品角色
class Alipay(Payment):
def __init__(self, use_huabei=False):
self.use_huabei = use_huabei
def pay(self, money):
if self.use_huabei == True:
print("花呗支付了{0}元!".format(money))
else:
print("支付宝余额支付了{0}元!".format(money))
# 产品角色
class WechatPay(Payment):
def pay(self, money):
print("微信支付了%d元!" % (money))
# 工厂类角色
class PaymentFactory:
def ctreate_payment(self, method):
if method == 'Alipay':
return Alipay()
elif method == 'WechatPay':
return WechatPay()
elif method == 'HuabeiPay':
return Alipay(use_huabei=True)
else:
raise TypeError('No such payment named %s' % method)
# 客户端调用。不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例
pf = PaymentFactory()
p = pf.ctreate_payment('HuabeiPay')
p.pay(100)
工厂方法(Factory Method)模式:
定义一个用于创建产品的接口,由子类决定生产什么产品。
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
模式的结构
工厂方法模式的主要角色如下
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
from abc import ABCMeta, abstractmethod
# 抽象产品角色
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 具体产品角色
class Alipay(Payment):
def __init__(self, use_huabei=False):
self.use_huabei = use_huabei
def pay(self, money):
if self.use_huabei == True:
print("花呗支付了{0}元!".format(money))
else:
print("支付宝余额支付了{0}元!".format(money))
class WechatPay(Payment):
def pay(self, money):
print("微信支付了%d元!" % (money))
# 抽象工厂角色
class PaymentFactory(metaclass=ABCMeta):
@abstractmethod
def create_payment(self):
pass
# 具体工厂角色
class AlipayFactory(PaymentFactory):
def create_payment(self):
return Alipay()
class WechatPayFactory(PaymentFactory):
def create_payment(self):
return Alipay()
class HuabeiFactory(PaymentFactory):
def create_payment(self):
return Alipay(use_huabei=True)
hfp = HuabeiFactory().create_payment()
hfp.pay(100) # 花呗支付了100元!
抽象工厂(AbstractFactory)模式:
提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
模式的结构
抽象工厂模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
from abc import ABCMeta, abstractmethod
# ------抽象的产品------
class PhoneShell(metaclass=ABCMeta):
@abstractmethod
def show_shell(self):
pass
class PhoneCPU(metaclass=ABCMeta):
@abstractmethod
def show_cpu(self):
pass
class PhoneOS(metaclass=ABCMeta):
@abstractmethod
def show_os(self):
pass
# ------具体的产品------
class SmallShell(PhoneShell):
def show_shell(self):
print('普通手机小手机壳')
class BigShell(PhoneShell):
def show_shell(self):
print('普通手机大手机壳')
class AppleShell(PhoneShell):
def show_shell(self):
print('苹果手机壳')
class SnapDragonCPU(PhoneCPU):
def show_cpu(self):
print('骁龙CPU')
class HuaweiCPU(PhoneCPU):
def show_cpu(self):
print('化为CPU')
class AppleCPU(PhoneCPU):
def show_cpu(self):
print('苹果CPU')
class AndroidOS(PhoneOS):
def show_os(self):
print('IOS系统')
class AppleOS(PhoneOS):
def show_os(self):
print('安卓系统')
# ------抽象的工厂------
class PhoneFactory(metaclass=ABCMeta):
@abstractmethod
def make_shell(self):
pass
@abstractmethod
def make_cpu(self):
pass
@abstractmethod
def make_os(self):
pass
# ------具体的工厂------
class HuaweiFactory(PhoneFactory):
def make_shell(self):
return SmallShell()
def make_cpu(self):
return HuaweiCPU()
def make_os(self):
return AndroidOS()
class AppleFactory(PhoneFactory):
def make_shell(self):
return AppleShell()
def make_cpu(self):
return AppleCPU()
def make_os(self):
return AppleOS()
# ------客户端------
class Phone:
def __init__(self, shell, cpu, os):
self.shell = shell
self.cpu = cpu
self.os = os
def show_info(self):
print('手机信息:')
self.shell.show_shell()
self.cpu.show_cpu()
self.os.show_os()
def make_phone(factory):
shell = factory.make_shell()
cpu = factory.make_cpu()
os = factory.make_os()
return Phone(shell, cpu, os)
p = make_phone(HuaweiFactory())
p.show_info()
"""
手机信息:
普通手机小手机壳
化为CPU
IOS系统
"""
建造者(Builder)模式:
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
优点:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
模式的结构
建造者(Builder)模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
from abc import ABCMeta, abstractmethod
# ------产品------
class Player:
def __init__(self, face=None, body=None, arms=None, legs=None):
self.face = face
self.body = body
self.arms = arms
self.legs = legs
def __str__(self):
return '%s,%s,%s,%s' % (self.face, self.body, self.arms, self.legs)
# ------抽象建造者------
class PlayerBuilder(metaclass=ABCMeta):
@abstractmethod
def build_face(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def build_arms(self):
pass
@abstractmethod
def build_legs(self):
pass
# ------具体建造者,隐藏了一个产品的内部结构------
class GirlBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = '漂亮的脸蛋'
def build_body(self):
self.player.body = '苗条的身材'
def build_arms(self):
self.player.arms = '细细的胳膊'
def build_legs(self):
self.player.legs = '大长腿'
# ------具体建造者,表示代码------
class MonsterBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = '绿脸'
def build_body(self):
self.player.body = '魁梧的身体'
def build_arms(self):
self.player.arms = '粗壮的胳膊'
def build_legs(self):
self.player.legs = '粗壮的大腿'
# ------指挥者,构造代码(构造代码和表示代码分开),可以对构造过程进行更加精细地控制------
class PlayerDirectory():
def builder_player(self, builder):
"""
隐藏了装配过程
:param builder:
:return:
"""
builder.build_face()
builder.build_body()
builder.build_arms()
builder.build_legs()
return builder.player
# ------客户端------
builder = GirlBuilder()
director = PlayerDirectory()
p = director.builder_player(builder)
print(p) # 漂亮的脸蛋,苗条的身材,细细的胳膊,大长腿
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。
它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
代理(Proxy)模式:
为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
缺点:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
模式的结构
代理模式的主要角色如下。
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
from abc import ABCMeta, abstractmethod
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
@abstractmethod
def set_content(self, content):
pass
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
print('读取文件内容!')
with open(self.filename, 'r', encoding='utf-8') as f:
self.content = f.read()
def get_content(self):
return self.content
def set_content(self, content):
with open(self.filename, 'w', encoding='utf-8') as f:
f.write(content)
subj = RealSubject('test.txt')
"""
读取文件内容!
"""
不使用虚代理,只要是实例化 RealSubject 类,就会读取这个文件占用内存。使用虚代理后,可以和根据需要创建对象,用户不调用是不会创建 RealSubject 对象的,节省了内存的开销。如果需要只有读的权限而没有写的权限,可以使用保护代理:
from abc import ABCMeta, abstractmethod
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
@abstractmethod
def set_content(self, content):
pass
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
print('读取文件内容!')
with open(self.filename, 'r', encoding='utf-8') as f:
self.content = f.read()
def get_content(self):
return self.content
def set_content(self, content):
with open(self.filename, 'w', encoding='utf-8') as f:
f.write(content)
class ProtectedSubject(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename)
def get_content(self):
return self.subj.get_content()
def set_content(self, content):
raise PermissionError('无写入权限!')
subj = ProtectedSubject('test.txt')
print(subj.get_content())
subj.set_content('abc')
"""
读取文件内容!
test file!
Traceback (most recent call last):
File "/home/thanlon/projects/PycharmProjects/untitled/代理模式.py", line 42, in <module>
subj.set_content('abc')
File "/home/thanlon/projects/PycharmProjects/untitled/代理模式.py", line 37, in set_content
raise PermissionError('无写入权限!')
PermissionError: 无写入权限!
"""
适配器(Adapter)模式:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 在很多业务场景中符合开闭原则。
缺点:
- 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
- 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
模式的结构
适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
# 类适配器模式使用示例:
from abc import ABCMeta, abstractmethod
# 目标接口
class Payment(object, metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def pay(self, money):
print('支付了%d' % money)
# 待适配的类
class BankPay():
def cost(self, money):
print('银联支付了%d' % money)
# 类适配器
class PaymentAdapter(Payment, BankPay):
"""
把不兼容cost转换成pay
"""
def pay(self, money):
self.cost(money)
p = PaymentAdapter()
p.pay(100)
"""
银联支付了100
"""
桥接(Bridge)模式:
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
优点:
- 抽象与实现分离,扩展能力强
- 符合开闭原则
- 符合合成复用原则
- 其实现细节对客户透明
缺点:
- 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
模式的结构
桥接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
from abc import ABCMeta, abstractmethod
# 抽象
class Shape(metaclass=ABCMeta):
def __init__(self, color):
self.color = color
@abstractmethod
def draw(self):
pass
# 实现
class Color(metaclass=ABCMeta):
@abstractmethod
def paint(self, shape):
pass
# 细化抽象
class Rectangle(Shape):
name = '长方形'
def draw(self):
self.color.paint(self)
# 如果要扩展形状,只需要添加形状类
class Circle(Shape):
name = '圆形'
def draw(self):
self.color.paint(self)
# 细化实现
class Red(Color):
def paint(self, shape):
print('画红色的%s' % shape.name)
# 如果要扩展颜色,只需要添加颜色类
class Green(Color):
def paint(self, shape):
print('画绿色的%s' % shape.name)
rectangle = Rectangle(Red())
rectangle.draw()
circle = Circle(Green())
circle.draw()
"""
画红色的长方形
画绿色的圆形
"""
装饰(Decorator)模式:
动态的给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:
为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
优点:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点:
- 不能很好地限制客户使用子系统类,很容易带来未知风险。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
模式的结构
外观(Facade)模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
# 子系统类
class CPU:
def run(self):
print('CPU start to run...')
def stop(self):
print('CPU stop to run...')
# 子系统类
class Disk:
def run(self):
print('Disk start to run...')
def stop(self):
print('Disk stop to run...')
# 子系统类
class Memory:
def run(self):
print('Memory start to run...')
def stop(self):
print('Memory stop to run...')
# 外观
class Computer():
def __init__(self):
self.CPU = CPU()
self.Disc = Disk()
self.Member = Memory()
def run(self):
self.CPU.run()
self.Disc.run()
self.Member.run()
def stop(self):
self.CPU.stop()
self.Disc.stop()
self.Member.stop()
# 客户端,高层代码
c = Computer()
c.run()
c.stop()
享元(Flyweight)模式:
运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:
将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
优点:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
模式的结构
组合模式包含以下主要角色。
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
from abc import ABCMeta, abstractmethod
# 抽象组件
class Graphic(metaclass=ABCMeta):
@abstractmethod
def draw(self):
pass
# 叶子组件
class Point(Graphic):
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return '点(%s,%s)' % (self.x, self.y)
def draw(self):
print(self)
# 叶子组件
class Line(Graphic):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def __str__(self):
return '线段[(%s,%s)]' % (self.p1, self.p2)
def draw(self):
print(self)
# 复合组件
class Picture(Graphic):
def __init__(self, iterable):
self.children = []
for g in iterable:
self.add(g)
def add(self, graphic):
self.children.append(graphic)
def draw(self):
for g in self.children:
g.draw()
# 简单图形
print('------简单图形------')
p = Point(1, 2)
l1 = Line(Point(1, 2), Point(3, 4))
l2 = Line(Point(5, 6), Point(7, 8))
print(p)
print(l1)
print(l2)
print('------复合图形(p,l1,l2)------')
# 复合图形
pic = Picture([p, l1, l2])
pic.draw()
行为型模式
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
模板方法(TemplateMethod)模式:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
优点:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
模式的结构
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型
- 抽象方法:在抽象类中声明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
from abc import ABCMeta, abstractmethod
from time import sleep
# 抽象类
class Window(metaclass=ABCMeta):
@abstractmethod
def start(self): # 原子操作/钩子操作
pass
@abstractmethod
def repaint(self): # 原子操作/钩子操作
pass
@abstractmethod
def stop(self): # 原子操作/钩子操作
pass
def run(self):
"""
模板方法(具体方法),这个大逻辑就不需要自己写了
:return:
"""
self.start()
while True:
try:
self.repaint()
sleep(1)
except KeyboardInterrupt:
break
self.stop()
# 具体类
class MyWindow(Window):
def __init__(self, msg):
self.msg = msg
def start(self):
print('窗口开始运行!')
def stop(self):
print('窗口停止运行!')
def repaint(self):
print(self.msg)
MyWindow("Hello...").run()
策略(Strategy)模式:
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类,增加维护难度。
模式的结构
策略模式的主要角色如下
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
from abc import abstractmethod, ABCMeta
from datetime import datetime
# 抽象策略
class Strategy(metaclass=ABCMeta):
@abstractmethod
def execute(self, data):
pass
# 具体策略
class FastStrategy(Strategy):
def execute(self, data):
print("使用较快的策略处理%s" % data)
# 具体策略
class SlowStrategy(Strategy):
def execute(self, data):
print("使用较慢的策略处理%s" % data)
# 上下文
class Context:
def __init__(self, strategy, data):
self.data = data
self.strategy = strategy
# 可以定义用户不知道的东西
self.date = datetime.now()
def set_strategy(self, strategy):
self.strategy = strategy
def do_strategy(self):
self.strategy.execute(self.data)
data = "Hello!"
# 使用较快的策略处理
fast_strategy = FastStrategy()
context = Context(fast_strategy, data)
context.do_strategy()
# 使用较慢的策略处理
slow_strategy = SlowStrategy()
context = Context(slow_strategy, data)
context.do_strategy()
"""
使用较快的策略处理Hello!
使用较慢的策略处理Hello!
"""
命令(Command)模式:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility)模式:
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
优点:
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
模式的结构
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
from abc import ABCMeta, abstractmethod
# 抽象的处理者
class Handler(metaclass=ABCMeta):
@abstractmethod
def handle_leave(self, day):
pass
# 具体的处理者
class GeneralManager(Handler):
def handle_leave(self, day):
if day <= 30:
print('总经理准假%d' % day)
else:
print('可以辞职了!')
# 具体的处理者
class DepartmentManager(Handler):
def __init__(self):
self.next = GeneralManager()
def handle_leave(self, day):
if day <= 7:
print('项目主管准假%d' % day)
else:
print('部门经理职权不足')
self.next.handle_leave(day)
# 具体的处理者
class ProjectDirector(Handler):
def __init__(self):
self.next = DepartmentManager()
def handle_leave(self, day):
if day <= 3:
print('项目主管准假%d' % day)
else:
print('项目主管职权不足')
self.next.handle_leave(day)
day = 20
p = ProjectDirector()
p.handle_leave(day)
"""
项目主管职权不足
部门经理职权不足
总经理准假20
"""
状态(State)模式:
允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer)模式:
多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
模式的结构
观察者模式的主要角色如下。
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
from abc import ABCMeta, abstractmethod
# 抽象的订阅者
class Observer(metaclass=ABCMeta):
@abstractmethod
def update(self, notice):
"""
:param notice: Notice类的对象
:return:
"""
pass
# 抽象的发布者:可以是接口,子类不需要实现,所以不需要定义抽象方法!
class Notice:
def __init__(self):
self.observers = []
def attach(self, obs):
self.observers.append(obs)
def detach(self, obs):
self.observers.remove(obs)
def notify(self):
"""
推送
:return:
"""
for obs in self.observers:
obs.update(self)
# 具体的发布者
class StaffNotice(Notice):
def __init__(self, company_info):
super().__init__() # 调用父类对象声明observers属性
self.__company_info = company_info
@property
def company_info(self):
return self.__company_info
@company_info.setter
def company_info(self, info):
self.__company_info = info
self.notify()
# 具体的订阅者
class Staff(Observer):
def __init__(self):
self.company_info = None
def update(self, notice):
self.company_info = notice.company_info
staff_notice = StaffNotice('初始化公司信息')
staff1 = Staff()
staff2 = Staff()
staff_notice.attach(staff1)
staff_notice.attach(staff2)
# print(staff1.company_info) None
# print(staff2.company_info) None
staff_notice.company_info = '假期放假通知!'
print(staff1.company_info)
print(staff2.company_info)
staff_notice.detach(staff2)
staff_notice.company_info = '明天开会!'
print(staff1.company_info)
print(staff2.company_info)
"""
假期放假通知!
假期放假通知!
明天开会!
假期放假通知!
"""
中介者(Mediator)模式:
定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator)模式:
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor)模式:
在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento)模式:
在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter)模式:
提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
参考链接
[1] 设计模式
[2] Python之常用设计模式
>