设计模式

视频地址:https://www.bilibili.com/video/BV1uA411N7c5

设计模式:对 软件设计 中普遍存在(反复出现)的各种问题所提出的解决方案。每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。

“四人帮(Gang of Four, GOF)”:

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
  • 写了一本书:《设计模式:可复用面向对象软件的基础》

1. 面向对象

面向对象的三大特征:

  1. 封装(类里面的,类外面的;私有和公有)
  2. 继承(主要用于复用代码)
  3. 多态(在Python中不需要care多态)

注意顺序,它们是递进关系

2. 接口(Interface)

接口:若干抽象方法的集合。

接口的作用

  1. 限制实现接口的类必须按照接口给定的调用方式实现这些方法
  2. 对高层模块隐藏了类的内部实现。

2.1 定义接口 —— 方法1

class Alipay:def pay(self, money):"""money: 支付的金额"""passclass WechatPay:def pay(self, money):passdef finish_pay(p, money):"""p: 支付对象"""p.pay(money)if __name__ == '__main__':# 创建支付对象p = Alipay()# 支付finish_pay(p, 100)"""可以发现,支付宝和微信的参数位置是一样的,这样我们在调用finish_pay时才可以方便,不用考虑参数是怎么样的。"""

既然这样,我们可以创建一个类,让AlipayWechatPay都继承这个类,并且完成它的方法。代码如下:

class Payment:"""定义一个接口,要求继承它的类必须实现它的方法"""def pay(self, money):raise NotImplementedErrorclass Alipay(Payment):def pay(self, money):passclass WechatPay(Payment):def pay(self, money):passif __name__ == '__main__':# 创建支付对象p = Alipay()p.pay(100)

但是这种方法有一个缺陷,当继承Payment的类没有实现pay方法时,如果不调用,那么也不会报错。

2.1 定义接口 —— 方法2

那么我们可以使用ABCMetaabstractmethod,代码如下:

ABC == abstract class, 抽象类

from abc import ABCMeta, abstractmethod  # ABC = Abstract Class, 抽象类class Payment(metaclass=ABCMeta):"""类继承ABCMeta,并且抽象方法使用@abstractmethod装饰器装饰,那么在继承Payment类时,必须实现这些用@abstractmethod装饰器装饰的方法,否则会报错!"""@abstractmethoddef pay(self, money):pass# class Alipay(Payment):
#     pass  # 这样写会报错# TypeError: Can't instantiate abstract class Alipay with abstract method pay
# TypeError: 不能实例化带有抽象方法pay的抽象类Alipayclass Alipay(Payment):def pay(self, money):print(f"支付宝支付{money}元")class WechatPay(Payment):def pay(self, money):passif __name__ == '__main__':# 创建支付对象# TypeError: Can't instantiate abstract class Alipay with abstract method pay# TypeError: 不能实例化带有抽象方法pay的抽象类Alipayp = Alipay()p.pay(100)  # 支付宝支付100元

3. 面向对象设计SOLID原则

  1. 单一职责原则(SRP):表明一个类有且只有一个职责。一个类就像容器一样,它能添加任意数量的属性、方法等。

  2. 开放封闭原则(OCP):一个类应该对扩展开放,对修改关闭。这意味一旦创建了一个类并且应用程序的其他部分开始使用它,就不应该修改它。

  3. 里氏替换原则(LSP):所有引用父类的地方必须能透明地使用其子类的对象。

  4. 接口隔离原则(ISP):表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的,即客户端不应该依赖那些它不需要的接口。

  5. 依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。这意味着不应该在高层模块中使用具体的低层模块。

    抽象就是接口
    简单来说就是先定义范式(接口),然后确定范式之间的调用关系,这样框架就搭建好了。

开放封闭原则:尽量不要修改源代码,而是需要什么再添加什么。

里氏替换原则:看下面这段代码。

class User:def show_name(self):passclass VIPUser(User):def show_name(self):# 更加高级的显示passdef show_user(u):res = u.show_name()passif __name__ == '__main__':tony = User()show_user(tony)

show_name方法是用来显示用户名的,VIPUser类中的show_name方法可能更加花里胡哨一些,但是里氏替换原则则要求UserVIPUsershow_name方法返回值的范式是一样的:都返回字符串、图片、gif等等。

如果两者返回值的范式是不一样的,那么show_user函数在调用u.show_name()得到,在后面的处理中就会报错!

接口隔离原则

from abc import ABCMeta, abstractmethodclass Animal(metaclass=ABCMeta):@abstractmethoddef walk(self):pass@abstractmethoddef swim(self):pass@abstractmethoddef fly(self):passclass Tiger(Animal):def walk(self):print("老虎走路...")def swim(self):print("老虎游泳...")# 老虎不会飞!因此接口Animal有问题def fly(self):pass

老虎不会飞!因此接口Animal有问题,因此我们接口的行为应该少一些,修改代码如下:

from abc import ABCMeta, abstractmethodclass LandAnimal(metaclass=ABCMeta):@abstractmethoddef walk(self):passclass WaterAnimal(metaclass=ABCMeta):@abstractmethoddef swim(self):passclass SkyAnimal(metaclass=ABCMeta):@abstractmethoddef fly(self):passclass 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. 设计模式分类

  1. 创建型模式(5种):聚焦在如何创建一个对象

    1. 工厂方法模式
    2. 抽象工厂模式
    3. 创建者模式
    4. 原型模式:基本上用不到
    5. 单例模式
  2. 结构型模式(7种):聚焦在几个类之间如何协同工作在一起(组合成什么结构)
    1. 适配器模式
    2. 桥模式
    3. 组合模式
    4. 装饰模式
    5. 外观模式
    6. 享元模式
    7. 代理模式
  3. 行为型模式(11种):聚焦在类的行为,主要侧重点是类的方法
    1. 解释器模式
    2. 责任链模式
    3. 命令模式
    4. 迭代器模式
    5. 中介者模式
    6. 备忘录模式
    7. 观察者模式
    8. 状态模式
    9. 策略模式
    10. 访问者模式
    11. 模板方法模式

上面这些有些会略过,因为设计模式的提出年代很老,当时主要聚焦在GUI上。

4.1 创建型模式(5种):聚焦在如何创建一个对象

简单工厂模式(不在23种设计模式之中)

把写类的人和创建对象的人分为两拨。创建类对象时传入参数即可,不需要知道这些参数在类中干了什么。

内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。

角色:

  • 工厂角色(Creator)
  • 抽象产品角色(Product)
  • 具体产品角色(Concrete Product)

简单工厂模式不在23种设计模式之中,因为它有一定的缺点。

例子如下:

from abc import ABCMeta, abstractmethodclass Payment(metaclass=ABCMeta):"""抽象产品角色"""@abstractmethoddef pay(self, money):passclass Alipay(Payment):"""具体产品角色"""def __init__(self, use_huabei=False):self.use_huabei = use_huabeidef 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, abstractmethodclass Payment(metaclass=ABCMeta):"""抽象产品角色(Product)"""@abstractmethoddef pay(self, money):passclass Alipay(Payment):"""具体产品角色(Concrete Product)"""def __init__(self, use_huabei=False):self.use_huabei = use_huabeidef 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) -> 工厂类的接口"""@abstractmethoddef create_payment(self):passclass 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):@abstractmethoddef show_shell(self):passclass CPU(metaclass=ABCMeta):@abstractmethoddef show_cpu(self):passclass OS(metaclass=ABCMeta):@abstractmethoddef show_os(self):pass"""----------抽象工厂------------"""
class PhoneFactory(metaclass=ABCMeta):@abstractmethoddef make_shell(self):pass@abstractmethoddef make_cpu(self):pass@abstractmethoddef 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 = cpuself.os = osself.shell = shelldef 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()"""手机信息: 麒麟CPUAndroid系统普通手机小手机壳"""p2 = make_phone(MiFactory())p2.show_info()"""手机信息: 骁龙CPUAndroid系统普通手机大手机壳"""p3 = make_phone(IPhoneFactory())p3.show_info()"""手机信息: 苹果CPUiOS系统苹果手机壳"""

优点:

  • 将客户端与类的具体实现相分离
  • 每个工厂创建了一个完整的产品系列,使得易于交换产品系列
  • 有利于产品的一致性(即产品之间的约束关系)

缺点:

  • 难以支持新种类的(抽象)产品 -> 再加一个内存的话,代码需要大改

抽象工厂模式现在用的比较少了!

3. 建造者模式

内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

角色:

  • 抽象建造者(Builder)
  • 具体建造者(Concrete Builder)
  • 指挥者(Director)
  • 产品(Product)

和抽象工厂模式有点像

from abc import ABCMeta, abstractmethodclass Player:def __init__(self, face=None, body=None, arm=None, leg=None):self.face = faceself.body = bodyself.arm = armself.leg = legdef __str__(self):return "%s, %s, %s, %s" % (self.face, self.body, self.arm, self.leg)"""----------抽象建造者(Builder)------------"""
class PlayerBuilder(metaclass=ABCMeta):@abstractmethoddef build_face(self):pass@abstractmethoddef build_body(self):pass@abstractmethoddef build_arm(self):pass@abstractmethoddef build_leg(self):passclass 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 Legbuilder_monster = MonsterBuilder()
monster = director.build_player(builder_monster)
print(monster)  # Bad Face, Fat, Monster Arm, Monster Leg

建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。

优点:

  • 隐藏了一个产品的内部结构和装配过程
  • 将构造代码与表示代码分开
  • 可以对构造过程进行更精细的控制

4. 原型模式

5. 单例模式

内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。

角色:

  • 单例(Singleton)

优点:

  • 对唯一实例的受控访问
  • 单例相当于全局变量,但防止了命名空间被污染
from abc import ABCMeta, abstractmethodclass Singleton:def __new__(cls, *args, **kwargs):"""在init之前执行"""if not hasattr(cls, "_instance"):  # 看一下类是否有"_instance"属性# 创建一个新的实例cls._instance = super(Singleton, cls).__new__(cls)# 返回类的实例return cls._instanceclass MyClass(Singleton):# 因为MyClass类继承了Singleton类,所以会执行Singleton里面的__new__方法def __init__(self, val):self.val = valif __name__ == '__main__':a = MyClass(10)b = MyClass(20)print(a.val)  # 20print(b.val)  # 20print(id(a), id(b))  # 1673238360128 1673238360128# 说明a和b都是同一个实例!这样创建类就可以确保这个类只有一个实例了!

创建型模式小结:

  • 抽象工厂模式和建造者模式相比于简单工厂模式和工厂方法模式而言更灵活也更复杂。
  • 通常情况下,设计以简单工厂模式或工厂方法模式开始,当你发现设计需要更大的灵活性时,则向更复杂的设计模式演化。

4.2 结构型模式(7种):聚焦在几个类之间如何协同工作在一起(组合成什么结构)

1. 适配器模式(Adaptor Mode)

内容:将一个类的接口转换为客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

两种实现方式:

  1. 类适配器:使用多继承
  2. 对象适配器:使用组合

例子:

from abc import ABCMeta, abstractmethodclass Payment(metaclass=ABCMeta):@abstractmethoddef pay(self, money):passclass 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:passclass 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 = paymentdef 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方法")

PaymentAdaptorNewBankPay相比而言,二者的作用对象不同:

  • PaymentAdaptor是对象适配器
  • NewBankPay是类适配器

整体代码如下:

from abc import ABCMeta, abstractmethodclass Payment(metaclass=ABCMeta):@abstractmethoddef pay(self, money):passclass 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 = paymentdef 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):代码中的BankPayApplePay
  • 适配器(Adaptor):代码中的NewBankPay(类适配器)和PaymentAdaptor(对象适配器)

适用场景:

  • 想使用一个已经存在的类,而它的接口不符合你的要求
  • (对象适配器)想使用一些已经存在的子类,但不可能对每一个都进行子类化以匹配它们的接口(不想每一个不兼容的类都写一个类适配器)。对象适配器可以适配它的父类接口。

2. 桥模式(Bridge Mode)

内容:将一个事物的两个维度分离,使其都可以独立地变化。

例子:

from abc import ABCMeta, abstractmethodclass Shape(metaclass=ABCMeta):def __init__(self, color: object):self.color = color@abstractmethoddef draw(self):passclass Color(metaclass=ABCMeta):@abstractmethoddef paint(self, shape: object):passclass 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()  # 绿色的圆形

这样写的好处就是,ShapeColor维度不是紧耦合的,两个维度可以任意扩展。

比如说我们可以再添加一个新的形状 —— 直线 和一个新的颜色 —— 蓝色:

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, abstractmethodclass Shape(metaclass=ABCMeta):def __init__(self, color: object):self.color = color@abstractmethoddef draw(self):passclass Color(metaclass=ABCMeta):@abstractmethoddef paint(self, shape: object):passclass 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, abstractmethodclass Graphic(metaclass=ABCMeta):"""抽象组件(Component)"""@abstractmethoddef draw(self):passclass Point(Graphic):"""叶子组件(Leaf)"""def __init__(self, x, y):self.x = xself.y = ydef __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 = p1self.p2 = p2def __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):@abstractmethoddef get_content(self):pass@abstractmethoddef set_content(self, content):pass"""------实体(RealSubject)------"""
class RealSubject(Subject):def __init__(self, filename):self.filename = filenamef = open(filename, 'r', encoding='utf-8')print("读取文件内容")self.content = f.read()f.close()def get_content(self):return self.contentdef 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 = filenameself.subj = Nonedef 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):@abstractmethoddef 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 = 2leader = ProjectDirectorHandler()leader.handle_leave(day)  # 项目主管准假2天day = 4leader.handle_leave(day)"""项目主管权限不足部门经理准假4天"""day = 8leader.handle_leave(day)"""项目主管权限不足部门经理权限不足总经理准假8天"""day = 15leader.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):@abstractmethoddef 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  # 公司的消息(私有对象)@propertydef company_info(self):return self.__company_info@company_info.setterdef company_info(self, info):self.__company_info = infoself.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 = nameself.company_info = Nonedef update(self, notice: Notice):self.company_info = notice.company_infoif __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. 算法1:考虑因素很多,因此可以匹配到最佳的司机,但此算法需要3分钟。
  2. 算法2:近似算法,该算法匹配到的司机可能不是最佳的,但此算法需要1分钟。

因此在订单高峰期的时候,会使用算法2以此加快订单的匹配速度;而在订单低峰期的时候,会使用算法1以此增加使用体验。

上面这种策略的切换封装起来就成了我们今天要讲的策略模式。

代码如下:

import datetime
from abc import ABCMeta, abstractmethod"""角色:+ 抽象策略(Strategy)+ 具体策略(ConcreteStrategy)+ 上下文(Context)
""""""---------抽象策略(Strategy)---------"""
class Strategy(metaclass=ABCMeta):@abstractmethoddef 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 = strategyself.data = dataself.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):@abstractmethoddef start(self):...@abstractmethoddef repaint(self):...@abstractmethoddef stop(self):  # 原子操作(钩子操作)...def run(self):  # 具体方法(模板方法) -> 并不是抽象接口self.start()while True:try:self.repaint()time.sleep(1)except KeyboardInterrupt:  # 当Ctrl + C停止运行时breakself.stop()"""---------具体类(Concrete Class)---------"""
class MyWindow(Window):def __init__(self, msg):self.msg = msgdef start(self):print("窗口开始运行")def repaint(self):print(self.msg)def stop(self):print("窗口结束运行")if __name__ == '__main__':MyWindow("Hello").run()"""窗口开始运行HelloHelloHelloHelloHello窗口结束运行"""

5. 设计模式总结

Python本身是动态语言,不像Java那样静态语言,因此在设计模式中不必过多的强求,因为Python并不是百分百适合设计模式。

Java几乎百分之百和设计模式契合

但Python也有50~60%的设计模式是契合的,所以说适当的引入一些设计模式对于写出漂亮的代码是很有帮助的。

[学习笔记] 4. 设计模式相关推荐

  1. 【学习笔记】设计模式-原型模式/克隆模式(Prototype)

    文章目录 0 设计模式 1 原型模式 2 实现思路 2.1 浅克隆 2.2 深克隆 3 需要的类 4 具体实现 4.1 浅克隆 4.2 深克隆 5 思维导图 6 示例源码地址 最后 0 设计模式 不了 ...

  2. 【学习笔记】设计模式-备忘录模式(Memento)

    文章目录 0 设计模式 1 备忘录模式 2 实现思路 3 需要的类 4 具体实现 4.1 Memento(备忘录) 4.2 Originator(目标对象) 4.3 CareTaker(管理者) 4. ...

  3. C++学习笔记之设计模式

    设计模式的概述 设计模式的定义:设计模式是在特定环境下人们解决某类重复出现的一套成功或有效的解决方案. 设计模式有什么用? 从高端来讲的话,作用大了去了.(网上是这么说的,虽然我还体会不到哈哈)那么对 ...

  4. 学习笔记:设计模式-装饰器设计模式(JAVA)

    概念 装饰器设计模式,也叫包装设计模式,他是作为现有类的一个包装,允许向一个现有的对象添加新的功能,同时又不改变其结构. 给对象添加功能,一般两种方式,继承或者组合,将一个类的对象嵌入到另一个对象中, ...

  5. [知了堂学习笔记]_设计模式之工厂模式

    介绍: 工厂模式专门负责将大量有共同接口的类实例化.工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类. 形态: 简单工厂(Simple Factory)模式,又称静态工厂方法模式 ...

  6. 7 种 Javascript 常用设计模式学习笔记

    7 种 Javascript 常用设计模式学习笔记 由于 JS 或者前端的场景限制,并不是 23 种设计模式都常用. 有的是没有使用场景,有的模式使用场景非常少,所以只是列举 7 个常见的模式 本文的 ...

  7. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  8. 设计模式学习笔记清单

    设计模式学习笔记清单 关于设计模式许多人已经耳熟能详,这段时间结合李建忠的教学视频以及大量网络资料,把这部分过了一遍,整理出学习笔记,而真正的深入学习和理解只能在具体的开发环境中日积月累.      ...

  9. 步步为营 .NET 设计模式学习笔记系列总结

    设计模式我从开篇到23种设计模式的讲解总共花了进两个月的时间,其间有很多读者给我提出了很好的建议,同时也指出了我的不足,对此我表示感谢,正是由于很多读者的支持我才能坚持的写到最后.在此表示我真诚的谢意 ...

最新文章

  1. Java如何等待子线程执行结束
  2. 【PAT (Advanced Level) Practice】PAT (Advanced Level) Practice
  3. 一道简单的编程题考核你的编程功底
  4. UIButton-内边距
  5. Windows Phone开发(4):框架和页
  6. [导入]FreeTextBox 1.6.3 中文版使用说明
  7. 技术管理:技术负责人所需的四个核心能力
  8. php 正则mysql语句_MySQL正则表达式搜索 - mysql数据库栏目 - 自学php
  9. 电气技术应用和计算机应用,电气技术应用专业介绍-1.ppt
  10. html5(mp4)无法播放,HTML5的Video标签有部分MP4无法播放的问题解析(多图)
  11. 《Go 语言程序设计》读书笔记(十)反射
  12. Creating a Pager Control for ASP.NET以及Dino Esposito 分页组件的一个 Bug
  13. el-table 树形表格 自定义展开图标_IconJar for Mac(图标素材设计软件)
  14. 【IBM Tivoli Identity Manager 学习文档】3 基本架构
  15. shell倒数第三位增加字符_shell中常用的变量处理、字符串操作(之三)
  16. 使用RomBuster获取你的网络路由器密码
  17. spss假设检验_SPSS有序Logistic回归的具体操作——「杏花开生物医药统计」
  18. OBJ 模型文件与MTL材质文件 介绍
  19. 游戏服务器租用阿里云和腾讯云价格对比
  20. 腾讯云数据库CDB介绍及数据库与应用数据库分析

热门文章

  1. Python列表的一些理解
  2. linux 没有 shutdown 命令
  3. PTA 快速排序(java/c++)
  4. 无限火力测试服连不上服务器,lol外服测试服更新 无限火力再次上线
  5. 钱诚10.28外汇黄金价格投资策略、美原油最新价格布局及指导
  6. 使用海蜘蛛HSpider模拟防火墙搭建网络案例说明v1.0
  7. python中单引号和双引号使用注意点
  8. mysql+odbc驱动安装,MySQL的ODBC驱动下载、安装以及配置数据源
  9. 超全在线客服网页特效素材网站整理
  10. 数组中hashCode就是内存地址,以及汉字幻化为16进制或10进制