视频地址:https://www.bilibili.com/video/BV1uA411N7c5
设计模式:对 软件设计 中普遍存在(反复出现)的各种问题所提出的解决方案。每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。
“四人帮(Gang of Four, GOF)”:
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
- 写了一本书:《设计模式:可复用面向对象软件的基础》
1. 面向对象
面向对象的三大特征:
- 封装(类里面的,类外面的;私有和公有)
- 继承(主要用于复用代码)
- 多态(在Python中不需要care多态)
注意顺序,它们是递进关系
2. 接口(Interface)
接口:若干抽象方法的集合。
接口的作用:
- 限制实现接口的类必须按照接口给定的调用方式实现这些方法
- 对高层模块隐藏了类的内部实现。
2.1 定义接口 —— 方法1
class Alipay:
def pay(self, money):
"""
money: 支付的金额
"""
pass
class WechatPay:
def pay(self, money):
pass
def finish_pay(p, money):
"""
p: 支付对象
"""
p.pay(money)
if __name__ == '__main__':
# 创建支付对象
p = Alipay()
# 支付
finish_pay(p, 100)
"""
可以发现,支付宝和微信的参数位置是一样的,这样我们在调用finish_pay时
才可以方便,不用考虑参数是怎么样的。
"""
既然这样,我们可以创建一个类,让Alipay
和WechatPay
都继承这个类,并且完成它的方法。代码如下:
class Payment:
"""定义一个接口,要求继承它的类必须实现它的方法"""
def pay(self, money):
raise NotImplementedError
class Alipay(Payment):
def pay(self, money):
pass
class WechatPay(Payment):
def pay(self, money):
pass
if __name__ == '__main__':
# 创建支付对象
p = Alipay()
p.pay(100)
但是这种方法有一个缺陷,当继承Payment
的类没有实现pay
方法时,如果不调用,那么也不会报错。
2.1 定义接口 —— 方法2
那么我们可以使用ABCMeta
和abstractmethod
,代码如下:
ABC == abstract class, 抽象类
from abc import ABCMeta, abstractmethod # ABC = Abstract Class, 抽象类
class Payment(metaclass=ABCMeta):
"""
类继承ABCMeta,并且抽象方法使用@abstractmethod装饰器装饰,
那么在继承Payment类时,必须实现这些用@abstractmethod装饰器装饰
的方法,否则会报错!
"""
@abstractmethod
def pay(self, money):
pass
# class Alipay(Payment):
# pass # 这样写会报错
# TypeError: Can't instantiate abstract class Alipay with abstract method pay
# TypeError: 不能实例化带有抽象方法pay的抽象类Alipay
class Alipay(Payment):
def pay(self, money):
print(f"支付宝支付{money}元")
class WechatPay(Payment):
def pay(self, money):
pass
if __name__ == '__main__':
# 创建支付对象
# TypeError: Can't instantiate abstract class Alipay with abstract method pay
# TypeError: 不能实例化带有抽象方法pay的抽象类Alipay
p = Alipay()
p.pay(100) # 支付宝支付100元
3. 面向对象设计SOLID原则
-
单一职责原则(SRP):表明一个类有且只有一个职责。一个类就像容器一样,它能添加任意数量的属性、方法等。
-
开放封闭原则(OCP):一个类应该对扩展开放,对修改关闭。这意味一旦创建了一个类并且应用程序的其他部分开始使用它,就不应该修改它。
-
里氏替换原则(LSP):所有引用父类的地方必须能透明地使用其子类的对象。
-
接口隔离原则(ISP):表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的,即客户端不应该依赖那些它不需要的接口。
-
依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。这意味着不应该在高层模块中使用具体的低层模块。
抽象就是接口
简单来说就是先定义范式(接口),然后确定范式之间的调用关系,这样框架就搭建好了。
开放封闭原则:尽量不要修改源代码,而是需要什么再添加什么。
里氏替换原则:看下面这段代码。
class User:
def show_name(self):
pass
class VIPUser(User):
def show_name(self):
# 更加高级的显示
pass
def show_user(u):
res = u.show_name()
pass
if __name__ == '__main__':
tony = User()
show_user(tony)
show_name
方法是用来显示用户名的,VIPUser
类中的show_name
方法可能更加花里胡哨一些,但是里氏替换原则则要求User
和VIPUser
的show_name
方法返回值的范式是一样的:都返回字符串、图片、gif等等。
如果两者返回值的范式是不一样的,那么show_user
函数在调用u.show_name()
得到,在后面的处理中就会报错!
接口隔离原则:
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
@abstractmethod
def walk(self):
pass
@abstractmethod
def swim(self):
pass
@abstractmethod
def fly(self):
pass
class Tiger(Animal):
def walk(self):
print("老虎走路...")
def swim(self):
print("老虎游泳...")
# 老虎不会飞!因此接口Animal有问题
def fly(self):
pass
老虎不会飞!因此接口Animal有问题,因此我们接口的行为应该少一些,修改代码如下:
from abc import ABCMeta, abstractmethod
class LandAnimal(metaclass=ABCMeta):
@abstractmethod
def walk(self):
pass
class WaterAnimal(metaclass=ABCMeta):
@abstractmethod
def swim(self):
pass
class SkyAnimal(metaclass=ABCMeta):
@abstractmethod
def fly(self):
pass
class Tiger(LandAnimal):
def walk(self):
print("老虎走路...")
class Frog(LandAnimal, WaterAnimal):
def walk(self):
print("青蛙走路...")
def swim(self):
print("青蛙游泳")
class Swan(LandAnimal, WaterAnimal, SkyAnimal):
def walk(self):
print("天鹅走路...")
def swim(self):
print("天鹅游泳...")
def fly(self):
print("天鹅飞行...")
将接口的行为设定为一个,那么我们在使用这些接口的时候就更加合适了。
一个类可以继承多个类(接口)
4. 设计模式分类
- 创建型模式(5种):聚焦在如何创建一个对象
- 工厂方法模式
- 抽象工厂模式
- 创建者模式
- 原型模式:基本上用不到
- 单例模式
- 结构型模式(7种):聚焦在几个类之间如何协同工作在一起(组合成什么结构)
- 适配器模式
- 桥模式
- 组合模式
- 装饰模式
- 外观模式
- 享元模式
- 代理模式
- 行为型模式(11种):聚焦在类的行为,主要侧重点是类的方法
- 解释器模式
- 责任链模式
- 命令模式
- 迭代器模式
- 中介者模式
- 备忘录模式
- 观察者模式
- 状态模式
- 策略模式
- 访问者模式
- 模板方法模式
上面这些有些会略过,因为设计模式的提出年代很老,当时主要聚焦在GUI上。
4.1 创建型模式(5种):聚焦在如何创建一个对象
简单工厂模式(不在23种设计模式之中)
把写类的人和创建对象的人分为两拨。创建类对象时传入参数即可,不需要知道这些参数在类中干了什么。
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。
角色:
- 工厂角色(Creator)
- 抽象产品角色(Product)
- 具体产品角色(Concrete Product)
简单工厂模式不在23种设计模式之中,因为它有一定的缺点。
例子如下:
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:
print(f"支付宝花呗支付{money}元")
else:
print(f"支付宝余额支付{money}元")
class WechatPay(Payment):
"""
具体产品角色
"""
def pay(self, money):
print(f"微信支付{money}元")
class PaymentFactory:
"""
工厂角色:用来生产支付对象的类
"""
def create_payment(self, method):
if method == 'alipay':
return Alipay()
elif method == 'wechat':
return WechatPay()
elif method == 'huabei':
return Alipay(use_huabei=True)
else:
raise TypeError(f"No such payment named {method}")
if __name__ == '__main__':
pf = PaymentFactory()
p = pf.create_payment('alipay')
p.pay(100) # 支付宝余额支付100元
p = pf.create_payment('huabei')
p.pay(100) # 支付宝花呗支付100元
- 优点:
- 隐藏了对象创建的实现细节
- 客户端不需要修改代码
- 缺点:
- 违反了单一职责原则,将创建逻辑集中到一个工厂类里
- 当添加新产品时,需要修改工厂类代码,违反了开闭原则
1. 工厂方法模式
内容:定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:
- 抽象工厂角色(Creator)
- 具体工厂角色(Concrete Creator)
- 抽象产品角色(Product)
- 具体产品角色(Concrete Product)
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
"""抽象产品角色(Product)"""
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
"""具体产品角色(Concrete Product)"""
def __init__(self, use_huabei=False):
self.use_huabei = use_huabei
def pay(self, money):
if self.use_huabei:
print(f"支付宝花呗支付{money}元")
else:
print(f"支付宝余额支付{money}元")
class WechatPay(Payment):
"""具体产品角色(Concrete Product)"""
def pay(self, money):
print(f"微信支付{money}元")
class Bankpay(Payment):
"""具体产品角色(Concrete Product)"""
def pay(self, money):
print(f"银行卡支付{money}元")
class PaymentFactory(metaclass=ABCMeta):
"""抽象工厂角色(Creator) -> 工厂类的接口"""
@abstractmethod
def create_payment(self):
pass
class AlipayFactory(PaymentFactory):
"""具体工厂角色(Concrete Creator)
创建支付宝的工厂类"""
def create_payment(self):
return Alipay()
class WechatFactory(PaymentFactory):
"""具体工厂角色(Concrete Creator)
创建微信支付的工厂类"""
def create_payment(self):
return WechatPay()
class HuabeiFactory(PaymentFactory):
"""具体工厂角色(Concrete Creator)
创建花呗支付的工厂类"""
def create_payment(self):
return Alipay(use_huabei=True)
class BankpayFactory(PaymentFactory):
"""具体工厂角色(Concrete Creator)
创建银行卡支付的工厂类"""
def create_payment(self):
return Bankpay()
if __name__ == '__main__':
pf = HuabeiFactory()
p = pf.create_payment()
p.pay(100) # 支付宝花呗支付100元
pf = BankpayFactory()
p = pf.create_payment()
p.pay(100) # 银行卡支付100元
- 优点:
- 每个具体产品都对应一个具体工厂类,不需要修改工厂类代码
- 隐藏了对象创建的实现细节
- 缺点:
- 每增加一个具体产品类,就必须增加一个相应的具体工厂类 -> 代码增加了
2. 抽象工厂模式
内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
例子:生产一部手机,需要手机壳、CPU、操作系统三类对象进行组装,其中每个类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。
相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品。
角色:
- 抽象工厂角色(Creator)
- 具体工厂角色(Concrete Creator)
- 抽象产品角色(Product)
- 具体产品角色(Concrete Product)
- 客户端(Client)
代码举例:
from abc import ABCMeta, abstractmethod
"""----------抽象产品------------"""
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
"""----------抽象工厂------------"""
class PhoneFactory(metaclass=ABCMeta):
@abstractmethod
def make_shell(self):
pass
@abstractmethod
def make_cpu(self):
pass
@abstractmethod
def make_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(CPU):
def show_cpu(self):
print("骁龙CPU")
class KirinCPU(CPU):
def show_cpu(self):
print("麒麟CPU")
class MediaTekCPU(CPU):
def show_cpu(self):
print("联发科CPU")
class AppleCPU(CPU):
def show_cpu(self):
print("苹果CPU")
class Android(OS):
def show_os(self):
print("Android系统")
class IOS(OS):
def show_os(self):
print("iOS系统")
"""----------具体工厂------------"""
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 KirinCPU()
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()
"""----------客户端------------"""
class Phone:
def __init__(self, cpu, os, shell):
self.cpu = cpu
self.os = os
self.shell = shell
def show_info(self):
print("手机信息: ")
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)
if __name__ == '__main__':
p1 = make_phone(HuaweiFactory())
p1.show_info()
"""
手机信息:
麒麟CPU
Android系统
普通手机小手机壳
"""
p2 = make_phone(MiFactory())
p2.show_info()
"""
手机信息:
骁龙CPU
Android系统
普通手机大手机壳
"""
p3 = make_phone(IPhoneFactory())
p3.show_info()
"""
手机信息:
苹果CPU
iOS系统
苹果手机壳
"""
优点:
- 将客户端与类的具体实现相分离
- 每个工厂创建了一个完整的产品系列,使得易于交换产品系列
- 有利于产品的一致性(即产品之间的约束关系)
缺点:
- 难以支持新种类的(抽象)产品 -> 再加一个内存的话,代码需要大改
抽象工厂模式现在用的比较少了!
3. 建造者模式
内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色:
- 抽象建造者(Builder)
- 具体建造者(Concrete Builder)
- 指挥者(Director)
- 产品(Product)
和抽象工厂模式有点像
from abc import ABCMeta, abstractmethod
class Player:
def __init__(self, face=None, body=None, arm=None, leg=None):
self.face = face
self.body = body
self.arm = arm
self.leg = leg
def __str__(self):
return "%s, %s, %s, %s" % (self.face, self.body, self.arm, self.leg)
"""----------抽象建造者(Builder)------------"""
class PlayerBuilder(metaclass=ABCMeta):
@abstractmethod
def build_face(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def build_arm(self):
pass
@abstractmethod
def build_leg(self):
pass
class GirlBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = "Pretty Face"
def build_body(self):
self.player.body = "Silm"
def build_arm(self):
self.player.arm = "Female Arm"
def build_leg(self):
self.player.leg = "Female Leg"
class MonsterBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = "Bad Face"
def build_body(self):
self.player.body = "Fat"
def build_arm(self):
self.player.arm = "Monster Arm"
def build_leg(self):
self.player.leg = "Monster Leg"
"""----------指挥者(Director)------------"""
class PlayerDirector:
def build_player(self, builder):
"""控制组装顺序"""
builder.build_body()
builder.build_face()
builder.build_arm()
builder.build_leg()
return builder.player
"""----------客户端------------"""
director = PlayerDirector()
builder_girl = GirlBuilder()
girl = director.build_player(builder_girl)
print(girl) # Pretty Face, Silm, Female Arm, Female Leg
builder_monster = MonsterBuilder()
monster = director.build_player(builder_monster)
print(monster) # Bad Face, Fat, Monster Arm, Monster Leg
建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。
优点:
- 隐藏了一个产品的内部结构和装配过程
- 将构造代码与表示代码分开
- 可以对构造过程进行更精细的控制
4. 原型模式
5. 单例模式
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
角色:
- 单例(Singleton)
优点:
- 对唯一实例的受控访问
- 单例相当于全局变量,但防止了命名空间被污染
from abc import ABCMeta, abstractmethod
class Singleton:
def __new__(cls, *args, **kwargs):
"""在init之前执行"""
if not hasattr(cls, "_instance"): # 看一下类是否有"_instance"属性
# 创建一个新的实例
cls._instance = super(Singleton, cls).__new__(cls)
# 返回类的实例
return cls._instance
class MyClass(Singleton):
# 因为MyClass类继承了Singleton类,所以会执行Singleton里面的__new__方法
def __init__(self, val):
self.val = val
if __name__ == '__main__':
a = MyClass(10)
b = MyClass(20)
print(a.val) # 20
print(b.val) # 20
print(id(a), id(b)) # 1673238360128 1673238360128
# 说明a和b都是同一个实例!这样创建类就可以确保这个类只有一个实例了!
创建型模式小结:
- 抽象工厂模式和建造者模式相比于简单工厂模式和工厂方法模式而言更灵活也更复杂。
- 通常情况下,设计以简单工厂模式或工厂方法模式开始,当你发现设计需要更大的灵活性时,则向更复杂的设计模式演化。
4.2 结构型模式(7种):聚焦在几个类之间如何协同工作在一起(组合成什么结构)
1. 适配器模式(Adaptor Mode)
内容:将一个类的接口转换为客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
两种实现方式:
- 类适配器:使用多继承
- 对象适配器:使用组合
例子:
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def pay(self, money):
print(f"支付宝支付{money}元")
class WechatPay(Payment):
def pay(self, money):
print(f"微信支付{money}元")
class BankPay:
def cost(self, money):
print(f"银联支付{money}元")
if __name__ == '__main__':
p = Alipay()
p.pay(100)
p = BankPay()
p.pay(100) # AttributeError: 'BankPay' object has no attribute 'pay'
此时,BankPay
的方法写的不规范,没有使用我们的Payment
接口,因此调用的时候发生了错误。
虽然我们可以修改BankPay
让它符合我们的预期,但如果BankPay
被其他程序调用了,我们一旦修改,其实地方就报错了,这样就不行了。此时我们就可以使用adaptor来进行修正。
class NewBankPay(Payment, BankPay):
"""Adaptor
继承Payment的目的是让接口统一
继承BankPay的目的是复用BankPay的代码
"""
def pay(self, money):
self.cost(money)
此时我们就可以使用NewBankPay
来替换BankPay
了。
假设我们有多个接口出现不兼容呢?
class ApplePay:
def spend(self, money):
print(f"Apple支付{money}元")
此时相当于有两个接口出现了不兼容,那我们还得为ApplePay
再写一个适配器。如果不兼容的接口多了,这样处理比较麻烦。
那Adaptor还有另外一种写法 —— 组合。组合的代码示意如下:
class A:
pass
class B:
def __init__(self):
self.a = A() # 我们在B类中创建一个A类的对象
def method_1(self):
self.a.方法() # 这样我们就可以在B类中调用A类的方法
# 这样我们就把A类和B类进行了组合
那么针对上面的问题,适配器代码如下:
class PaymentAdaptor(Payment):
"""Adaptor(对象适配器)
继承Payment的目的是接口一致
"""
def __init__(self, payment: object):
self.payment = payment
def pay(self, money):
if hasattr(self.payment, "pay"):
self.payment.pay(money)
elif hasattr(self.payment, "spend"):
self.payment.spend(money)
elif hasattr(self.payment, "cost"):
self.payment.cost(money)
else:
raise NotImplementedError("没有实现cost或spend方法")
PaymentAdaptor
和NewBankPay
相比而言,二者的作用对象不同:
PaymentAdaptor
是对象适配器NewBankPay
是类适配器
整体代码如下:
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class Alipay(Payment):
def pay(self, money):
print(f"支付宝支付{money}元")
class WechatPay(Payment):
def pay(self, money):
print(f"微信支付{money}元")
class BankPay:
def cost(self, money):
print(f"银联支付{money}元")
class ApplePay:
def spend(self, money):
print(f"Apple支付{money}元")
class NewBankPay(Payment, BankPay):
"""Adaptor(类适配器)
继承Payment的目的是让接口统一
继承BankPay的目的是复用BankPay的代码
"""
def pay(self, money):
self.cost(money)
class PaymentAdaptor(Payment):
"""Adaptor(对象适配器)
继承Payment的目的是接口一致
"""
def __init__(self, payment: object):
self.payment = payment
def pay(self, money):
if hasattr(self.payment, "pay"):
self.payment.pay(money)
elif hasattr(self.payment, "spend"):
self.payment.spend(money)
elif hasattr(self.payment, "cost"):
self.payment.cost(money)
else:
raise NotImplementedError("没有实现cost或spend方法")
if __name__ == '__main__':
p = Alipay()
p.pay(100) # 支付宝支付100元
# p = BankPay()
# p.pay(100) # AttributeError: 'BankPay' object has no attribute 'pay'
p = NewBankPay()
p.pay(100) # 银联支付100元
p = PaymentAdaptor(ApplePay())
p.pay(100) # Apple支付100元
p = PaymentAdaptor(BankPay())
p.pay(100) # 银联支付100元
p = PaymentAdaptor(WechatPay())
p.pay(100) # 微信支付100元
角色:
- 目标接口(Target):代码中的
Payment
接口 - 待适配的类(Adaptee):代码中的
BankPay
和ApplePay
- 适配器(Adaptor):代码中的
NewBankPay
(类适配器)和PaymentAdaptor
(对象适配器)
适用场景:
- 想使用一个已经存在的类,而它的接口不符合你的要求
- (对象适配器)想使用一些已经存在的子类,但不可能对每一个都进行子类化以匹配它们的接口(不想每一个不兼容的类都写一个类适配器)。对象适配器可以适配它的父类接口。
2. 桥模式(Bridge Mode)
内容:将一个事物的两个维度分离,使其都可以独立地变化。
例子:
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
def __init__(self, color: object):
self.color = color
@abstractmethod
def draw(self):
pass
class Color(metaclass=ABCMeta):
@abstractmethod
def paint(self, shape: object):
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: object):
print(f"红色的{shape.name}")
class Green(Color):
def paint(self, shape: object):
print(f"绿色的{shape.name}")
if __name__ == '__main__':
shape = Rectangle(color=Red())
shape.draw() # 红色的长方形
shape = Circle(color=Green())
shape.draw() # 绿色的圆形
这样写的好处就是,Shape
和Color
维度不是紧耦合的,两个维度可以任意扩展。
比如说我们可以再添加一个新的形状 —— 直线 和一个新的颜色 —— 蓝色:
class Line(Shape):
name = "直线"
def draw(self):
# 直线逻辑
self.color.paint(self)
class Blue(Color):
def paint(self, shape: object):
print(f"蓝色的{shape.name}")
角色:
- 抽象(Abstraction):
Shape
- 细化抽象(Refined Abstraction):
Rectangle
,Circle
,Line
- 实现者(Implementor):
Color
- 具体实现者(Concrete Implementor):
Red
,Green
,Blue
应用场景:
- 当事物有两个维度上的表现,两个维度可能扩展时。
优点:
- 抽象和实现相分离
- 优秀的扩展能力
整体代码如下:
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
def __init__(self, color: object):
self.color = color
@abstractmethod
def draw(self):
pass
class Color(metaclass=ABCMeta):
@abstractmethod
def paint(self, shape: object):
pass
class Rectangle(Shape):
name = "长方形"
def draw(self):
# 长方形逻辑
self.color.paint(self)
class Circle(Shape):
name = "圆形"
def draw(self):
# 圆形逻辑
self.color.paint(self)
class Line(Shape):
name = "直线"
def draw(self):
# 直线逻辑
self.color.paint(self)
class Red(Color):
def paint(self, shape: object):
print(f"红色的{shape.name}")
class Green(Color):
def paint(self, shape: object):
print(f"绿色的{shape.name}")
if __name__ == '__main__':
shape = Rectangle(color=Red())
shape.draw() # 红色的长方形
shape = Circle(color=Green())
shape.draw() # 绿色的圆形
shape = Line(color=Red())
shape.draw() # 红色的直线
3. 组合模式(Composite Mode)
内容:将对象组合成树形结构以表示“部分——整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
角色:
- 抽象组件(Component):
Graphic
- 叶子组件(Leaf):
Point
,Line
- 复合组件(Composite):
Picture
- 客户端(Client):
__main__
from abc import ABCMeta, abstractmethod
class Graphic(metaclass=ABCMeta):
"""抽象组件(Component)"""
@abstractmethod
def draw(self):
pass
class Point(Graphic):
"""叶子组件(Leaf)"""
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def draw(self):
print(str(self))
class Line(Graphic):
"""叶子组件(Leaf)"""
def __init__(self, p1: Point, p2: Point):
self.p1 = p1
self.p2 = p2
def __str__(self):
return f"线段: [{self.p1}, {self.p2}]"
def draw(self):
print(self)
class Picture(Graphic):
"""复合组件(Composite)"""
def __init__(self, iterable):
self.children = []
if iterable:
for g in iterable:
self.add(g)
def add(self, graphic):
self.children.append(graphic)
def draw(self):
print("--------复合图形--------")
if self.children:
for g in self.children:
g.draw()
print("--------复合图形--------")
if __name__ == '__main__':
"""客户端(Client)"""
line = Line(Point(1, 1), Point(2, 2))
print(line) # 线段: [(1, 1), (2, 2)]
line.draw() # 线段: [(1, 1), (2, 2)]
print()
p1 = Point(2, 3)
line_1 = Line(p1=Point(3, 4), p2=Point(6, 7))
line_2 = Line(p1=Point(1, 5), p2=Point(2, 8))
pic_1 = Picture(iterable=[p1, line_1, line_2])
pic_1.draw()
print()
"""
--------复合图形--------
(2, 3)
线段: [(3, 4), (6, 7)]
线段: [(1, 5), (2, 8)]
--------复合图形--------
"""
p2 = Point(4, 4)
line_3 = Line(p1=Point(1, 1), p2=Point(0, 0))
pic_2 = Picture(iterable=[p2, line_3])
pic_total = Picture(iterable=[pic_1, pic_2])
pic_total.draw()
"""
--------复合图形--------
--------复合图形--------
(2, 3)
线段: [(3, 4), (6, 7)]
线段: [(1, 5), (2, 8)]
--------复合图形--------
--------复合图形--------
(4, 4)
线段: [(1, 1), (0, 0)]
--------复合图形--------
--------复合图形--------
"""
适用场景:
- 表示对象的“部分——整体”层次结构(特别是结构是递归的)
- 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象
优点:
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户端代码,即客户端可以一致地使用组合对象和单个对象
- 更容易增加新类型的组件
4. 装饰模式
5. 外观模式(Facade Mode)
内容:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
角色:
- 外观(Facade)
- 子系统类(Subsystem Classes)
子系统代码如下:
class CPU:
def run(self):
print(f"CPU开始运行")
def stop(self):
print(f"CPU停止运行")
class Disk:
def run(self):
print("硬盘开始工作")
def stop(self):
print("硬盘停止工作")
class Memory:
def run(self):
print("内存通电")
def stop(self):
print("内存断电")
我们可以定义一个更高级的系统(Facade)来调用子系统:
class Computer:
def __init__(self):
self.cpu = CPU()
self.disk = Disk()
self.memory = Memory()
def run(self):
self.cpu.run()
self.disk.run()
self.memory.run()
def stop(self):
self.cpu.stop()
self.disk.stop()
self.memory.stop()
整体代码如下:
"""---------子系统类(Subsystem Classes)-----------"""
class CPU:
def run(self):
print(f"CPU开始运行")
def stop(self):
print(f"CPU停止运行")
class Disk:
def run(self):
print("硬盘开始工作")
def stop(self):
print("硬盘停止工作")
class Memory:
def run(self):
print("内存通电")
def stop(self):
print("内存断电")
"""---------外观(Facade)-----------"""
class Computer:
def __init__(self):
self.cpu = CPU()
self.disk = Disk()
self.memory = Memory()
def run(self):
self.cpu.run()
self.disk.run()
self.memory.run()
def stop(self):
self.cpu.stop()
self.disk.stop()
self.memory.stop()
"""---------客户端(Client)-----------"""
if __name__ == '__main__':
computer = Computer()
computer.run()
"""
CPU开始运行
硬盘开始工作
内存通电
"""
computer.stop()
"""
CPU停止运行
硬盘停止工作
内存断电
"""
6. 享元模式
7. 代理模式
内容:为其他对象提供一种代理以控制对这个对象的访问。
应用场景:
- 远程代理:为远程的对象提供代理
- 虚代理:根据需要创建很大的对象
- 保护代理:控制对原始对象的访问,用于对象有不同访问权限时
角色:
- 抽象实体(Subject)
- 实体(RealSubject)
- 代理(Proxy)
优点:
- 远程代理:可以隐藏对象位于远程地址空间的事实
- 虚拟代理:可以进行优化,例如根据要求创建对象
- 保护代理:允许在访问一个对象时有一些附加的内务处理
from abc import ABCMeta, abstractmethod
"""------抽象实体(Subject)------"""
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
@abstractmethod
def set_content(self, content):
pass
"""------实体(RealSubject)------"""
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
f = open(filename, 'r', encoding='utf-8')
print("读取文件内容")
self.content = f.read()
f.close()
def get_content(self):
return self.content
def set_content(self, content):
f = open(self.filename, 'w', encoding='utf-8')
f.write(content)
f.close()
"""------虚拟代理------"""
class VirtualProxy(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()
def set_content(self, content):
if not self.subj:
self.subj = RealSubject(self.filename)
return self.subj.set_content(content)
"""------保护代理------"""
class ProtectedProxy(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("无写入权限")
if __name__ == '__main__':
# subj = RealSubject("test.txt")
subj = VirtualProxy("test.txt")
print(subj.get_content())
subj = ProtectedProxy("test.txt")
print(subj.get_content())
subj.set_content("123") # PermissionError: 无写入权限
4.3 行为型模式(11种):聚焦在类的行为,主要侧重点是类的方法
1. 解释器模式
2. 责任链模式
内容:使多个对象都有机会处理请求,从而避免情况的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该情况,直到有一个对象处理它为止。
在公司请假,一般是向直系的Leader请假,但Leader的权限只能请一天两天,大于这个天数需要Leader向他的Leader申请(部门Leader);而部门Leader的权限是一个星期,如果大于这个时间,需要部门Leader向他的上级申请(总经理)。
角色:
- 抽象处理者(Handler)
- 具体处理者(ConcreteHandler)
- 客户端(Client)
适用场景:
- 有多个对象可以处理同一个情况,哪个对象处理由运行时决定
- 在不明确接收者的情况下,向多个对象中的一个提交一个请求
优点:
- 降低耦合度:一个对象无需知道是其他哪一个对象处理其请求
from abc import ABCMeta, abstractmethod
"""----------抽象处理者(Handler)----------"""
class Handler(metaclass=ABCMeta):
@abstractmethod
def handle_leave(self, day):
...
"""----------具体处理者(ConcreteHandler)----------"""
class GeneralManagerHandler(Handler):
def handle_leave(self, day):
if day <= 10:
print(f"总经理准假{day}天")
else:
print("总经理不准假")
"""----------具体处理者(ConcreteHandler)----------"""
class DepartmentManagerHandler(Handler):
def __init__(self):
self.next = GeneralManagerHandler()
def handle_leave(self, day):
if day <= 5:
print(f"部门经理准假{day}天")
else:
print("部门经理权限不足")
self.next.handle_leave(day)
"""----------具体处理者(ConcreteHandler)----------"""
class ProjectDirectorHandler(Handler):
def __init__(self):
self.next = DepartmentManagerHandler()
def handle_leave(self, day):
if day <= 3:
print(f"项目主管准假{day}天")
else:
print("项目主管权限不足")
self.next.handle_leave(day)
"""----------客户端(Client)----------"""
if __name__ == '__main__':
day = 2
leader = ProjectDirectorHandler()
leader.handle_leave(day) # 项目主管准假2天
day = 4
leader.handle_leave(day)
"""
项目主管权限不足
部门经理准假4天
"""
day = 8
leader.handle_leave(day)
"""
项目主管权限不足
部门经理权限不足
总经理准假8天
"""
day = 15
leader.handle_leave(day)
"""
项目主管权限不足
部门经理权限不足
总经理不准假
"""
3. 命令模式
4. 迭代器模式
5. 中介者模式
6. 备忘录模式
7. 观察者模式
内容:定义对象间的一种 [一对多] 的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。观察者模式又称为“发布——订阅”模式。
角色:
- 抽象主题(Subject) -> 抽象发布者
- 具体主题(Concrete Subject) -> 具体发布者
- 抽象观察者(Observer)-> 抽象订阅者
- 具体观察者(Concrete Observer)-> 具体订阅者
发布者和订阅者的关系一定是松耦合的,因为可以取消订阅
适用场景:
- 当一个抽象模型有两方面,其中一方面依赖于另一方面。将这两者封装在独立对象中以使得它们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的
优点:
- 目标和观察者之间的抽象耦合最小
- 支持广播通信
示例代码如下:
from abc import ABCMeta, abstractmethod
"""
角色:
+ 抽象主题(Subject) -> 抽象发布者
+ 具体主题(Concrete Subject) -> 具体发布者
+ 抽象观察者(Observer)-> 抽象订阅者
+ 具体观察者(Concrete Observer)-> 具体订阅者
"""
"""---------抽象订阅者---------"""
class Observer(metaclass=ABCMeta):
@abstractmethod
def update(self, notice): # notice是一个Notice类的对象
...
"""---------抽象发布者---------"""
class Notice:
def __init__(self):
# 维护一个列表,里面存储所有的订阅者
self.observer = []
def attach(self, obs: Observer): # 订阅
self.observer.append(obs)
def detach(self, obs: Observer): # 取消订阅
try:
self.observer.remove(obs)
except Exception as e:
print(f"取消订阅失败:{e}")
def notify(self): # 通知每一个观察者 -> 推送
for obs in self.observer:
obs.update(self)
"""---------具体发布者---------"""
class StaffNotice(Notice):
def __init__(self, company_info):
super().__init__()
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()
"""
obj = StaffNotice("abc")
# @property
print(obj.company_info) # abc
# @company_info.setter
obj.company_info = "123"
print(obj.company_info) # 123
"""
"""---------具体订阅者---------"""
class Staff(Observer):
def __init__(self, name):
self.name = name
self.company_info = None
def update(self, notice: Notice):
self.company_info = notice.company_info
if __name__ == '__main__':
# 创建Notice对象
notice = StaffNotice("初始公司通知")
# 创建员工
s1 = Staff("Tom")
s2 = Staff("Jerry")
# 绑定员工(订阅)
notice.attach(s1)
notice.attach(s2)
# 查看通知
print(s1.company_info) # None
# 发布新的订阅
notice.company_info = "公司今年业绩非常好,给大家发奖金!"
# 查看通知
print(s1.company_info) # 公司今年业绩非常好,给大家发奖金!
# 取消订阅
notice.detach(s2)
# 发布新的订阅
notice.company_info = "公司明天放假!"
# 查看通知
print(s1.company_info) # 公司明天放假!
print(s2.company_info) # 公司今年业绩非常好,给大家发奖金!
8. 状态模式
9. 策略模式
内容:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
角色:
- 抽象策略(Strategy)
- 具体策略(ConcreteStrategy)
- 上下文(Context)
优点:
- 定义了一系列可重用的算法和行为
- 消除了一些条件语句
- 可以提供相同行为的不同实现
缺点:
- 客户必须了解不同的策略
举个例子:滴滴打车。有一个用户需要打车,本质上是人匹配车的问题,而且需要考虑的因素有很多(距离、司机的评分、乘客的信誉、价格等等),因此需要使用算法。假设算法我们有了,而且有两套:
- 算法1:考虑因素很多,因此可以匹配到最佳的司机,但此算法需要3分钟。
- 算法2:近似算法,该算法匹配到的司机可能不是最佳的,但此算法需要1分钟。
因此在订单高峰期的时候,会使用算法2以此加快订单的匹配速度;而在订单低峰期的时候,会使用算法1以此增加使用体验。
上面这种策略的切换封装起来就成了我们今天要讲的策略模式。
代码如下:
import datetime
from abc import ABCMeta, abstractmethod
"""
角色:
+ 抽象策略(Strategy)
+ 具体策略(ConcreteStrategy)
+ 上下文(Context)
"""
"""---------抽象策略(Strategy)---------"""
class Strategy(metaclass=ABCMeta):
@abstractmethod
def execute(self, data): # 执行策略, data为算法所需的数据
...
"""---------具体策略(ConcreteStrategy)---------"""
class FastStrategy(Strategy):
def execute(self, data):
print(f"用较快的策略处理{data}")
"""---------具体策略(ConcreteStrategy)---------"""
class SlowStrategy(Strategy):
def execute(self, data):
print(f"用较慢的策略处理{data}")
"""---------上下文(Context)---------"""
class Context:
"""
作用:再封装一层,把数据和策略通过上下文类传进去
Note: Context中也可以放一些不需要调用者知道的参数,如日期等
"""
def __init__(self, strategy: Strategy, data):
self.strategy = strategy
self.data = data
self.date = datetime.datetime.now()
# 切换策略
def set_strategy(self, strategy: Strategy):
self.strategy = strategy
# 执行策略
def do_strategy(self):
self.strategy.execute(self.data)
"""---------客户端(Client)---------"""
if __name__ == '__main__':
data = "[距离, 司机的评分, 乘客的信誉, 价格]"
s1 = FastStrategy()
s2 = SlowStrategy()
# 创建上下文实例
context = Context(strategy=s1, data=data)
# 执行策略
context.do_strategy() # 用较快的策略处理[距离, 司机的评分, 乘客的信誉, 价格]
# 切换策略
context.set_strategy(strategy=s2)
# 执行策略
context.do_strategy() # 用较慢的策略处理[距离, 司机的评分, 乘客的信誉, 价格]
10. 访问者模式
11. 模板方法模式
内容:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可以重定义该算法的某些特定步骤。
角色:
- 抽象类(Abstract Class):
- 定义抽象的原子操作(钩子操作)
- 实现一个模板方法作为算法的骨架。
- 具体类(Concrete Class): 实现原子操作
使用场景:
- 一次性实现一个算法的不变的部分
- 各个子类中的公共行为应该被提取出来并集中到一个公共父类中以避免代码重复
- 控制子类扩展
import time
from abc import ABCMeta, abstractmethod
"""
角色:
+ 抽象类(Abstract Class):
+ 定义抽象的原子操作(钩子操作)
+ 实现一个模板方法作为算法的骨架。
+ 具体类(Concrete Class): 实现原子操作
"""
"""---------抽象类(Abstract Class)---------"""
class Window(metaclass=ABCMeta):
@abstractmethod
def start(self):
...
@abstractmethod
def repaint(self):
...
@abstractmethod
def stop(self): # 原子操作(钩子操作)
...
def run(self): # 具体方法(模板方法) -> 并不是抽象接口
self.start()
while True:
try:
self.repaint()
time.sleep(1)
except KeyboardInterrupt: # 当Ctrl + C停止运行时
break
self.stop()
"""---------具体类(Concrete Class)---------"""
class MyWindow(Window):
def __init__(self, msg):
self.msg = msg
def start(self):
print("窗口开始运行")
def repaint(self):
print(self.msg)
def stop(self):
print("窗口结束运行")
if __name__ == '__main__':
MyWindow("Hello").run()
"""
窗口开始运行
Hello
Hello
Hello
Hello
Hello
窗口结束运行
"""
5. 设计模式总结
Python本身是动态语言,不像Java那样静态语言,因此在设计模式中不必过多的强求,因为Python并不是百分百适合设计模式。
Java几乎百分之百和设计模式契合
但Python也有50~60%的设计模式是契合的,所以说适当的引入一些设计模式对于写出漂亮的代码是很有帮助的。