Python_设计模式
每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次地使用该方案而不必做重复劳动。—— Christopher Alexander
软件危机催生了设计模式,面向对象,软件工程。
是什么
每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。
设计模式四个基本要素
模式名称、问题、解决方案、效果
热身
既然是针对面向对象的,让我们先来回顾一下面向的三大特性和接口
面向对象的三大特性:封装,继承和多态
封装
把数据和函数包装在类里,类的边界限制外界的访问,将类内和类外隔绝开,并视情况提供一些接口。
尤其是一些关键的变量(比如在类内部计算出来的变量),建议设置为私有变量,隔绝类外的访问和修改。
class A:
def __init__(self, name):
self.__name = name
def getname(self): # 接口
return self.__name
def setname(self, name): # 接口
self.__name = name
class B(A):
def __init__(self):
print(self.__name)
b = B() # 报错
私有变量只有类内才能访问,类外不能,亲儿子也不可以!
这里,我们让B继承A,实例化,得到以下出错信息。这是因为私有变量在定义阶段就会发生变形,原理复习这里
AttributeError: 'B' object has no attribute '_B__name'
继承和多态
继承是为了实现代码复用,并且子类可以重写父类的方法或者派生自己的属性。Python语言本身就是多态的,程序员不需要关心。
接口:
接口是一种特殊的类,声明了若干方法,要求继承该接口的类必须实现这些方法。
作用:限制继承接口的类的方法的名称及调用方式;隐藏了类的内部实现。
python中实现接口有两种方式:抽象类和 raise NotImplemented
# 方式一:父类中定义方法,应用raise NotImplemented约束
class BaseClient(object):
def __init__(self):
self.api = settings.API
def execute(self):
raise NotImplemented("子类必须实现execute方法")
class AgentClient(BaseClient): #子类必须实现raise NotImplemented约束的方法
def execute(self):
obj = Plugin()
res = obj.execute_plugin()
self.post_data(res)
# 方式二:抽象类
from abc import abstractmethod, ABCMeta
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
"""支付方法,参数money"""
pass
#实现Payment接口
class Alipay(Payment):
def pay(self, money):
print("支付宝支付%s元"%money)
class ApplePay(Payment):
def pay(self, money):
print("苹果支付%s元"%money)
设计模式六大原则
-
开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
-
里氏(Liskov)替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
# 如果我在下面的高层代码def func中引用了基类User的对象,那么我一定也可以传入User子类VIPUser的对象,不论子类是继承还是重写了父类中的方法。 class User: def check(self): print('checking') return True class VIPUser(User): def check(self): print('vip-chating') return True def chat(self): print('Chatting') def func(user): res = user.check() -
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程(需求提出后,先定义接口,作为约束,以后不管底层实现如何变,也不会影响到高层)。
-
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
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("Tiger Walk") def swim(self): print("Tiger Walk") def fly(self): print("Tiger Walk")以上肯定不合理,老虎会飞吗?因此fly这个方法Tiger类根不不需要,但是接口却约束子类必须实现。正确的应该是定义三个动物类,让子类进行多继承:
class LandAnimal(metaclass=ABCMeta): @abstractmethod def walk(self): pass class SkyAnimal(metaclass=ABCMeta): @abstractmethod def fly(self): pass class WaterAnimal(metaclass=ABCMeta): @abstractmethod def swim(self): pass class Tiger(LandAnimal): def walk(self): print("Tiger Walk") class Frog(WaterAnimal, LandAnimal): def walk(self): print('Frog Walk') def swim(self): print('Frog swim') -
迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用,也就是解耦。
-
单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
设计模式分类
创建型模式
简单工厂模式
- 内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。工厂类属于底层代码,客户端指的是高层代码。
- 角色:
- 工厂角色(Creator),隐藏对象创建的细节
- 抽象产品角色(Product),定义接口
- 具体产品角色(Concrete Product),实现接口,。
- 优点:
- 隐藏了对象创建的实现细节;
- 客户端不需要修改代码
- 缺点:
- 违反了单一职责原则,将创建逻辑集中到一个工厂类里;
- 当添加新产品时,需要修改工厂类代码,违反了开闭原则
from abc import abstractmethod, ABCMeta
# 抽象产品角色
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 具体产品角色
class Alipay(Payment):
def __init__(self, enable_yuebao=False):
self.enable_yuebao = enable_yuebao
def pay(self, money):
if self.enable_yuebao:
print("余额宝支付%s元" % money)
else:
print("支付宝支付%s元" % money)
# 具体产品角色
class ApplePay(Payment):
def pay(self, money):
print("苹果支付%s元" % money)
# 工厂角色
class PaymentFactory:
def create_payment(self, method):
if method == "alipay":
return Alipay()
elif method == 'yuebao':
return Alipay(enable_yuebao=True)
elif method == "applepay":
return ApplePay()
else:
raise NameError(method)
# 通过工厂类创建产品类的实例
f = PaymentFactory()
p = f.create_payment("yuebao")
p.pay(100)
工厂方法模式
- 内容:定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
- 角色:
- 抽象工厂角色(Creator),定义工厂类接口
- 具体工厂角色(Concrete Creator),隐藏对象创建细节
- 抽象产品角色(Product),定义产品接口
- 具体产品角色(Concrete Product),实现产品接口
- 特点:工厂方法模式相比简单工厂模式将每个具体产品都对应了一个具体工厂。
- 适用场景:
- 需要生产多种、大量复杂对象的时候
- 需要降低耦合度的时候
- 当系统中的产品种类需要经常扩展的时候
- 优点:
- 每个具体产品都对应一个具体工厂类,不需要修改工厂类代码
- 隐藏了对象创建的实现细节
- 缺点:
- 每增加一个具体产品类,就必须增加一个相应的具体工厂类
from abc import abstractmethod, ABCMeta
# 抽象产品角色
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 具体产品角色
class Alipay(Payment):
def pay(self, money):
print("支付宝支付%s元" % money)
# 具体产品角色
class ApplePay(Payment):
def pay(self, money):
print("苹果支付%s元"%money)
# 抽象工厂角色
class PaymentFactory(metaclass=ABCMeta):
@abstractmethod
def create_payment(self):
pass
# 具体工厂角色
class AlipayFactory(PaymentFactory):
def create_payment(self):
return Alipay()
# 具体工厂角色
class ApplePayFactory(PaymentFactory):
def create_payment(self):
return ApplePay()
# 先实例化具体工厂类,再由具体工厂类实例化具体产品
af = AlipayFactory()
ali = af.create_payment()
ali.pay(120)
抽象工厂模式
- 内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。例:生产一部手机,需要手机壳、CPU、操作系统三类对象进行组装,其中每类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。
- 角色:
- 抽象工厂角色(Creator)
- 具体工厂角色(Concrete Creator)
- 抽象产品角色(Product)
- 具体产品角色(Concrete Product)
- 客户端(Client)
- 特点:相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品。该模式只负责生成,组装由客户端负责
- 使用场景:
- 系统要独立于产品的创建与组合时
- 强调一系列相关的产品对象的设计以便进行联合使用时
- 提供一个产品类库,想隐藏产品的具体实现时
- 优点:
- 将客户端与类的具体实现相分离
- 每个工厂创建了一个完整的产品系列,使得易于交换产品系列
- 有利于产品的一致性(即产品之间的约束关系)
- 缺点:
- 难以支持新种类的(抽象)产品。比如加一个电池,所有都要大改。
from abc import abstractmethod, ABCMeta
# ------抽象产品------
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 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系统")
# ------具体工厂------
# 创建一系列相关或相互依赖的对象,有利于产品一致性,比如Iphone不能用联发科,Mi不能用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 MediaTekCPU()
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)
p1 = make_phone(HuaweiFactory())
p1.show_info()
创建者模式
- 内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 角色:
- 抽象建造者(Builder)
- 具体建造者(Concrete Builder),隐藏内部结构
- 指挥者(Director),隐藏装配过程
- 产品(Product)
- 特点:建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。
- 使用场景:
- 当创建复杂对象的算法(Director)应该独立于该对象的组成部分以及它们的装配方式(Builder)时
- 当构造过程允许被构造的对象有不同的表示时(不同Builder)。
- 优点:
- 隐藏了一个产品的内部结构和装配过程
- 将构造代码与表示代码分开
- 可以对构造过程进行更精细的控制
from abc import abstractmethod, ABCMeta
#------产品------
class Player: # 游戏角色
def __init__(self, face=None, body=None, arm=None, leg=None):
self.face = face
self.arm = arm
self.leg = leg
self.body = body
def __str__(self):
return "%s, %s, %s, %s" % (self.face, self.arm, self.body, self.leg)
#------建造者------
# 抽象建造者
class PlayerBuilder(metaclass=ABCMeta): # 捏脸
@abstractmethod
def build_face(self):
pass
@abstractmethod
def build_arm(self):
pass
@abstractmethod
def build_leg(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def get_player(self):
pass
# 具体建造者
class BeautifulWomanBuilder(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = "漂亮脸蛋"
def build_arm(self):
self.player.arm="细胳膊"
def build_body(self):
self.player.body="细腰"
def build_leg(self):
self.player.leg="长腿"
def get_player(self):
return self.player
#------指挥者------
class PlayerDirector:
def build_player(self, builder):
builder.build_body()
builder.build_arm()
builder.build_leg()
builder.build_face()
return builder.get_player()
director = PlayerDirector() # setp1: 实例化指挥者
builder = BeautifulWomanBuilder() # step2: 实例化建造者
p = director.build_player(builder) # step3: 指挥者指挥建造者创建游戏角色
print(p)
原型模式
暂不讨论,略
单例模式
- 内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
- 角色:单例(Singleton)
- 适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
- 优点:
- 对唯一实例的受控访问
- 单例相当于全局变量,但防止了命名空间被污染
- 其它:与单例模式功能相似的概念:全局变量、静态变量(方法)
- 单例模式的几种实现方式:
__new__,装饰器,文件单例
方式一:_new_
这里先了解下__new__和__init__的区别:感谢这位同学的整理
class A(object):
def __init__(self):
print "init"
def __new__(cls,*args, **kwargs):
print "new %s"%cls
return object.__new__(cls, *args, **kwargs)
A()
"""
new <class '__main__.A'>
init
"""
几点说明:
- 继承自object的新式类才有`new_``
- ``new`至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值- 若
__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行
class A(object):
pass
class B(A):
def __init__(self):
print "init"
def __new__(cls,*args, **kwargs):
print "new %s"%cls
return object.__new__(A, *args, **kwargs)
b=B()
print type(b)
"""
new <class '__main__.B'>
<class '__main__.A'>
"""
通过__new__实现单例模式:
class Singleton(object):
def __new__(cls, *args, **kwargs):
# 如果当前类没有_instance属性,那么就调用父类的__new__方法实例化对象,新增_instance属性并赋值
if not hasattr(cls, "_instance"):
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
class MyClass(Singleton):
def __init__(self, name):
self.name = name
a = MyClass("a")
print(a) # <__main__.MyClass object at 0x0000025D076AD3C8>
print(a.name) # a
b = MyClass('b')
print(b) # <__main__.MyClass object at 0x0000025D076AD3C8>
print(b.name) # b
b.name = 'xxx'
print(a) # <__main__.MyClass object at 0x0000025D076AD3C8>
print(a.name) # xxx
方式二:通过装饰器
在装饰器中,我们通过拦截类的__new__实现,判断该类是否存在于__dict__字典中,如果存在则返回该类的实例,不存在则实例化该类并且存放于__dict__中。
# 方式一:
def singleton(cls, *args, **kw):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class myclass:
pass
class1 = myclass()
class2 = myclass()
assert class1 == class2
# 方式二:和上面装饰器思路是一样的。只是写到类方法里。
class Foo:
_instance = None
def __init__(self):
pass
@classmethod
def get_instance(cls):
if cls._instance:
return cls._instance
else:
obj = cls()
cls._instance = obj
return obj
# 创建实例
obj = Foo.get_instance()
方式三:文件单例模式:
# step1: 在一个py文件中定义一个单例类并实例化
class Singleton(object):
def __init__(self):
pass
def foo(self):
pass
def bar(self):
pass
instance = Singleton()
# step2: 在其它文件中导入实例化对象,每次都是操作同一个实例
from Singleton import instance
instance.foo()
instance.bar()
文件单例模式的特点:
- 程序一开始的时候就创建了单例类的实例
- 如果有人不遵守规定使用已经创建好的实例,而是自己创建则无法实现单例。
创建模式小结
23.png
结构型模式
适配器模式
-
内容:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。就单来说就是实现接口一致和代码复用,如果接口不一致,写一个转接头。
-
角色:
- 目标接口(Target)
- 待适配的类(Adaptee)
- 适配器(Adapter)
-
两种实现方式:
- 类适配器:使用多继承
- 对象适配器:使用组合
24.png
-
适用场景:
- 想使用一个已经存在的类,而它的接口不符合要求
- (对象适配器)想使用一些已经存在的子类,但不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
from abc import abstractmethod, ABCMeta
# 目标接口
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
raise NotImplementedError
class Alipay(Payment):
def pay(self, money):
print("支付宝支付%s元"%money)
class ApplePay(Payment):
def pay(self, money):
print("苹果支付%s元"%money)
#------待适配类------
class WechatPay:
def cost(self, money):
print("微信支付%s元"%money)
#类适配器
class RealWechatPay(WechatPay, Payment):
def pay(self, money):
return self.cost(money)
# 对象适配器
class RealWechatPay2(Payment):
def __init__(self):
self.payment = WechatPay()
def pay(self, money):
return self.payment.cost(money)
p = RealWechatPay2()
p.pay(111)
注意:
- 为什么不直接更改源码呢?因为其他地方可能调用了源码。
- 如果差的太远,可能无法适配。
浙公网安备 33010602011771号