视频地址:https://www.bilibili.com/video/BV1uA411N7c5
设计模式:对 软件设计 中普遍存在(反复出现)的各种问题所提出的解决方案。每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。
“四人帮(Gang of Four, GOF)”:
面向对象的三大特征:
注意顺序,它们是递进关系
接口:若干抽象方法的集合。
接口的作用:
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时才可以方便,不用考虑参数是怎么样的。"""
既然这样,我们可以创建一个类,让Alipay
和WechatPay
都继承这个类,并且完成它的方法。代码如下:
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
方法时,如果不调用,那么也不会报错。
那么我们可以使用ABCMeta
和abstractmethod
,代码如下:
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元
单一职责原则(SRP):表明一个类有且只有一个职责。一个类就像容器一样,它能添加任意数量的属性、方法等。
开放封闭原则(OCP):一个类应该对扩展开放,对修改关闭。这意味一旦创建了一个类并且应用程序的其他部分开始使用它,就不应该修改它。
里氏替换原则(LSP):所有引用父类的地方必须能透明地使用其子类的对象。
接口隔离原则(ISP):表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的,即客户端不应该依赖那些它不需要的接口。
依赖倒置原则(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
方法可能更加花里胡哨一些,但是里氏替换原则则要求User
和VIPUser
的show_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("天鹅飞行...")
将接口的行为设定为一个,那么我们在使用这些接口的时候就更加合适了。
一个类可以继承多个类(接口)
上面这些有些会略过,因为设计模式的提出年代很老,当时主要聚焦在GUI上。
把写类的人和创建对象的人分为两拨。创建类对象时传入参数即可,不需要知道这些参数在类中干了什么。
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。
角色:
简单工厂模式不在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元
内容:定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:
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元
内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
例子:生产一部手机,需要手机壳、CPU、操作系统三类对象进行组装,其中每个类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。
相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品。
角色:
代码举例:
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系统苹果手机壳"""
优点:
缺点:
抽象工厂模式现在用的比较少了!
内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色:
和抽象工厂模式有点像
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
建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。
优点:
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
角色:
优点:
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都是同一个实例!这样创建类就可以确保这个类只有一个实例了!
创建型模式小结:
内容:将一个类的接口转换为客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
两种实现方式:
例子:
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方法")
PaymentAdaptor
和NewBankPay
相比而言,二者的作用对象不同:
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元
角色:
Payment
接口BankPay
和ApplePay
NewBankPay
(类适配器)和PaymentAdaptor
(对象适配器)适用场景:
内容:将一个事物的两个维度分离,使其都可以独立地变化。
例子:
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() # 绿色的圆形
这样写的好处就是,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}")
角色:
Shape
Rectangle
, Circle
, Line
Color
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() # 红色的直线
内容:将对象组合成树形结构以表示“部分——整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
角色:
Graphic
Point
, Line
Picture
__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)]--------复合图形----------------复合图形--------"""
适用场景:
优点:
内容:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
角色:
子系统代码如下:
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停止运行硬盘停止工作内存断电"""
内容:为其他对象提供一种代理以控制对这个对象的访问。
应用场景:
角色:
优点:
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: 无写入权限
内容:使多个对象都有机会处理请求,从而避免情况的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该情况,直到有一个对象处理它为止。
在公司请假,一般是向直系的Leader请假,但Leader的权限只能请一天两天,大于这个天数需要Leader向他的Leader申请(部门Leader);而部门Leader的权限是一个星期,如果大于这个时间,需要部门Leader向他的上级申请(总经理)。
角色:
适用场景:
优点:
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)"""项目主管权限不足部门经理权限不足总经理不准假"""
内容:定义对象间的一种 [一对多] 的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。观察者模式又称为“发布——订阅”模式。
角色:
发布者和订阅者的关系一定是松耦合的,因为可以取消订阅
适用场景:
优点:
示例代码如下:
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) # 公司今年业绩非常好,给大家发奖金!
内容:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
角色:
优点:
缺点:
举个例子:滴滴打车。有一个用户需要打车,本质上是人匹配车的问题,而且需要考虑的因素有很多(距离、司机的评分、乘客的信誉、价格等等),因此需要使用算法。假设算法我们有了,而且有两套:
因此在订单高峰期的时候,会使用算法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() # 用较慢的策略处理[距离, 司机的评分, 乘客的信誉, 价格]
内容:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可以重定义该算法的某些特定步骤。
角色:
使用场景:
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窗口结束运行"""
Python本身是动态语言,不像Java那样静态语言,因此在设计模式中不必过多的强求,因为Python并不是百分百适合设计模式。
Java几乎百分之百和设计模式契合
但Python也有50~60%的设计模式是契合的,所以说适当的引入一些设计模式对于写出漂亮的代码是很有帮助的。