设计模式及Python实现
设计模式是什么?
Christopher Alexander:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心*。这样你就能一次又一次地使用该方案而不必做重复劳动。”
设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。最为重要的是,设计模式也是一把双刃剑,如果设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。
起初,你会认为“模式”就是为了解决一类特定问题而特别想出来的明智之举。说的没错,看起来的确是通过很多人一起工作,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案。也许这些问题你曾经见过或是曾经解决过,但是你的解决方案很可能没有模式这么完备。
虽然被称为“设计模式”,但是它们同“设计“领域并非紧密联系。设计模式同传统意义上的分析、设计与实现不同,事实上设计模式将一个完整的理念根植于程序中,所以它可能出现在分析阶段或是更高层的设计阶段。很有趣的是因为设计模式的具体体现是程序代码,因此可能会让你认为它不会在具体实现阶段之前出现(事实上在进入具体实现阶段之前你都没有意识到正在使用具体的设计模式)。
可以通过程序设计的基本概念来理解模式:增加一个抽象层。抽象一个事物就是隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来。当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考那些不会变动的设计方法了。这么做不仅会使代码可维护性更高,而且会让代码更易于理解,从而降低开发成本。
三种最基本的设计模式:
- 创建模式:提供实例化的方法,为适合的状况提供相应的对象创建方法。
- 结构化模式:通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
- 行为模式:用于在不同的实体间进行通信,为实体之间的通信提供更容易,更灵活的通信方法。
设计模式六大原则
- 开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
- 里氏(Liskov)替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
- 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
- 接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
- 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。
- 单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
接口
接口:一种特殊的类,声明了若干方法,要求继承该接口的类必须实现这些方法。
作用:限制继承该接口的类的方法的名称及调用方式;隐藏了类的内部实现。
接口就是一种抽象的基类(父类),限制继承它的类必须实现接口中定义的某些方法。
Python中使用ABCMeta
、abstractmethod
的抽象类、抽象方法来实现接口的功能。接口类定义方法,不具体实现,限制子类必须有该方法。在接口子类中实现具体的功能。
# 通过抽象类和抽象方法,做抽象用
from abc import ABCMeta
from abc import abstractmethod # 导入抽象方法
class Father(metaclass=ABCMeta): # 创建抽象类
@abstractmethod
def f1(self):
pass
@abstractmethod
def f2(self):
pass
class F1(Father):
def f1(self):
pass
def f2(self):
pass
def f3(self):
pass
obj = F1()
报错定义接口
class Interface:
def method(self, arg):
raise NotImplementedError
一、创建型模式
创建模式可以在直接使用类的构造方法实例化对象并不方便时,为开发者提供更好的实例化对象的方式。
1. 简单工厂模式
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。
角色:
- 工厂角色(Creator)
- 抽象产品角色(Product)
- 具体产品角色(Concrete Product)
优点:
- 隐藏了对象创建的实现细节
- 客户端不需要修改代码
缺点:
- 违反了单一职责原则,一个工厂类中实现了多种创建逻辑
- 当添加新产品时,需要修改工厂类代码,违反了开闭原则
PaymentFactory简单工厂
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)
2. 工厂方法模式(Factory Method)
内容:定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:
- 抽象工厂角色(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)
3. 抽象工厂方法(Abstract Factory)
内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
例:生产一部手机,需要手机壳、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系统")
# ------具体工厂------
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()
4. 建造者模式(Builder)
内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色:
- 抽象建造者(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()
builder = BeautifulWomanBuilder()
p = director.build_player(builder)
print(p)
5. 单例模式(Singleton)
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
角色:
- 单例(Singleton)
适用场景
-
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
-
单例模式也可以用于对应用中共享的资源或功能的访问进行限制.
比如数据库的连接对象或者记录日志的对象就可以用单例模式创建, 全局使用一份, 不仅避免了每次重新实例化对象浪费的资源, 也便于应用整体的管理.
优点:
- 对唯一实例的受控访问
- 单例相当于全局变量,但防止了命名空间被污染。
与单例模式功能相似的概念:全局变量、静态变量(方法)
常见误区与注意事项
首先, 为了保证一个类的对象在应用中只有一份, 我们除了要在实例化的过程中进行限制以外, 还需要注意禁止对这类对象的拷贝.
另外, Python中使用单例模式的一大误区就是依靠重写__new__
方法的方式实现单例模式. 这种写法可能在一般使用情况下没有问题, 但当需要继承扩展使用单例模式的类时就会出现一些无法预期的效果. 虽然单例类一般不应该被继承扩展, 尤其不该有多层继承, 但是了解可能出现的错误以及如何避免可以让代码更加的健壮.
示例代码
一、不完美写法(重写 __new__
方法)
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
class Child(Singleton):
pass
def identity_test():
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
exit(0)
def create_parent_first():
s1 = Singleton()
c1 = Child()
print(c1 is s1) # True
exit(0)
def create_child_first():
c1 = Child()
s1 = Singleton()
print(c1 is s1) # False
exit(0)
从以上示例代码的执行结果中可以看出重写 __new__
方法实现单例模式的类被继承时的一些问题:
- 当系统中同时需要父类和子类, 假如先实例化父类, 后实例化子类, 它们其实指向的是同一个对象, 这显然是不合理的.
- 而当先实例化子类, 后实例化父类时, 它们指向的却又不是同一个对象. 在复杂的系统中, 父类和子类谁先被实例化可能是不确定的, 这样就带来了未知的隐患, 而这种问题导致的bug也非常的难排查.
注意:在交互解释器中连续执行create_parent_first()
和create_child_first()
, 在执行第二个函数时由于内存中已经有了实例化的对象, 会显示错误的结果.
二、完美写法
1、单例模式的正确写法应该是使用元类. 首先定义一个单例元类, 然后在声明需要使用单例模式的类时指定单例元类作为元类.
class SingletonType(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DatabaseConnector(metaclass=SingletonType):
pass
class MySQLConnector(DatabaseConnector):
pass
def identity_test():
d1 = DatabaseConnector()
d2 = DatabaseConnector()
m1 = MySQLConnector()
m2 = MySQLConnector()
print(d1 is d2) # True
print(m1 is m2) # True
exit(0)
def create_parent_first():
d = DatabaseConnector()
m = MySQLConnector()
print(d is m) # False
exit(0)
def create_child_first():
m = MySQLConnector()
d = DatabaseConnector()
print(d is m) # False
exit(0)
从执行结果中可以看出, 在错误写法中出现的问题在使用元类实现单例时都不存在, 子类与父类永远指向不同的单例对象, 不受实例化顺序的影响.
2、装饰器方法
def singleton(cls, *args, **kw):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return get_instance
@singleton
class MyClass2:
a = 1
one = MyClass2()
two = MyClass2()
print(id(one)) # 31495472
print(id(two)) # 31495472
print(one == two)
print(one is two)
Python开发者对于单例模式的不同看法
单例模式在Python中是比较受争议的一种设计模式. 许多开发者认为使用单例模式来保证程序中某些东西的唯一性的写法过于臃肿, 在Python中可以借助语言的一些特性用更自然, 更符合Python风格的写法实现类似的功能.
同时, 在一些场景中应该谨慎使用或者避免单例模式, 比如分布式计算, 自动化测试等. 这些场景中使用单例模式可能反而会产生负面效果.
要不要用单例模式, 怎么用单例模式要根据具体情况而定. 我个人认为, 重要的不是实现方式, 而是实现方式背后单例模式的思想.
Python中代替单例模式的方法——>Borg
Borg(也叫做Monostate)是由Alex Martelli提出的一种实现类似单例模式行为的写法, 它并不是传统设计模式中的一种.
这种写法的核心思想是: 不限制一个类有多少的实例, 只要类的所有实例的状态都是共享的, 同样可以实现类似单例模式中的统一性.
具体实现就是在使用Borg实例化新对象时, 所有对象都共享一个__dict__
.
使用Borg也可以解决错误实现方式中的继承问题. 但是Borg写法也有着自己的隐患. 当继承Borg的子类中重写了__getattr__
方法可能出现问题.
class Borg:
_state = {}
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
obj.__dict__ = cls._state
return obj
Borg示例
class DatabaseConnector:
_state = {}
def __new__(cls, db, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
obj.__dict__ = cls._state
return obj
def __init__(self, db):
self.db = db
if __name__ == "__main__":
d1 = DatabaseConnector("MySQL")
d2 = DatabaseConnector("PostgreSQL")
print(d1.db) # PostgreSQL
print(d2.db) # PostgreSQL
使用模块实现单例模式
Python中的模块可以用于代替上面提到的几种写法实现单例模式, 因为Python中的模块本身已经是单例了.
最常见的用法就是在模块中实例化需要作为单例使用的对象并赋值给模块中的作用域是整个模块的变量, 然后开发者在需要时直接从模块中导入这个对象来使用而不自己手动实例化.
这种写法相对而言并不稳妥, 因为我们可以随时给指向单例对象的变量重新赋值, 也依然可以直接实例化对象.
但是换个角度来看, 这种做法也让我们的代码更加的灵活, 我们可以写文档注释来提示其他开发者某些对象应该作为单例来使用, 但当他们有其它需求时也可以直接实例化创建多个对象。
# Python的模块是天然的单例模式。
# module_name.py
class MySingleton(object):
def foo(self):
print('danli')
my_singleton = MySingleton()
# to use
from .module_name import my_singleton
my_singleton.foo()
print(id(my_singleton))
from .module_name import my_singleton
my_singleton.foo()
print(id(my_singleton))
6. 原型模式(Prototype)
内容:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。在Python中最简单的原型模式可以使用copy
模块中的deepcopy
函数来实现,它接收一个对象作为参数,然后返回这个对象的拷贝对象。正是因为在Python中拷贝是内置功能,使用起来非常方便和自然,因此在使用时也较少被称为一种模式。
使用场景:
- 通过动态装载;
- 为了避免创建一个与产品类层次平行的工厂类层次时;
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
-
当系统中已有一个对象时,如果需要修改它的一些属性,但同时我们不想直接对它做出修改,此时就可以使用原型模式。以已有对象作为原型创建拷贝,然后修改和使用拷贝的对象。
比如在Django中,
HttpRequest
对象中的GET
和POST
是QueryDict
类型的对象,它默认是不可变的。 如图所示:虽然
HttpRequest
的初始化方法中创建QueryDict
时设置为了可变,但在视图中修改request.GET
时会报错。所以当我们需要修改
request.GET
时,就可以创建一份拷贝。拷贝得到的对象是可变的,我们可以对拷贝的对象进行修改后使用。使用QueryDict
对象的copy
方法就可以创建拷贝。从源码截图中可以看出,copy`方法内部调用的还是Python内置的深拷贝。
原型模式示例代码:
import copy
class Prototype:
def __init__(self):
self._objects = {}
def register_object(self, name, obj):
"""Register an object"""
self._objects[name] = obj
def unregister_object(self, name):
"""Unregister an object"""
del self._objects[name]
def clone(self, name, **attr):
"""Clone a registered object and update inner attributes dictionary"""
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attr)
return obj
def main():
class A:
def __str__(self):
return "I am A"
a = A()
prototype = Prototype()
prototype.register_object('a', a)
b = prototype.clone('a', a=1, b=2, c=3)
print(a)
print(b.a, b.b, b.c)
if __name__ == '__main__':
main()
创建型模式总结
使用抽象工厂(Abstract Factory)、原型(Prototype)或者建造者(Builder)的设计甚至比工厂方法(Factory Method)的那些设计更灵活,但它们也更加复杂。通常,设计以使用工厂方法(Factory Method)开始。并且当设计者发现需要更大的灵活性时,设计便会想其他创建模式烟花。当你在设计标准之间权衡的时候,了解多个模式可以给你提供给更多的选择余地。
依赖于继承的创建型模式:工厂方法模式
依赖于组合的创建型模式:抽象工厂模式、创建者模式
二、结构性模式
这些设计模式在大型应用中十分重要,它们为开发者提供了划分代码组织结构的方法,以及如何让应用中的各个部分协同工作的方法。
1. 适配器模式(Adapter Class/Object)
内容:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
角色:
- 目标接口(Target)
- 待适配的类(Adaptee)
- 适配器(Adapter)
两种实现方式:
- 类适配器:使用多继承
- 对象适配器:使用组合
适用场景:
- 想使用一个已经存在的类,而它的接口不符合你的要求
- (对象适配器)想使用一些已经存在的子类,但不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
类适配器:
- 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有他的子类时,类Adaptee将不能胜任工作。
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
- 仅仅引入一个对象,并不需要额外的指针以间接得到Adaptee。
对象适配器:
- 允许一个Adapter与多个Adaptee——即Adaptee本身以及它所有的子类(如果有子类的话)一同时工作。Adapter也可以一次给所有的Adaptee添加功能。
- 使得重定义Adaptee的行为比较困难。这酒需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
适配器模式示例代码:
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)
2. 组合模式(Composite)
内容:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
角色:
- 抽象组件(Component)
- 叶子组件(Leaf)
- 复合组件(Composite)
- 客户端(Client)
适用场景:
- 表示对象的“部分-整体”层次结构(特别是结构是递归的)
- 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象
优点:
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户端代码,即客户端可以一致地使用组合对象和单个对象
- 更容易增加新类型的组件
缺点:
- 很难限制组合中的组件
组合模式示例代码:
from abc import abstractmethod, ABCMeta
class Graphic(metaclass=ABCMeta):
@abstractmethod
def draw(self):
pass
@abstractmethod
def add(self, graphic):
pass
def getchildren(self):
pass
# 图元
class Point(Graphic):
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print(self)
def add(self, graphic):
raise TypeError
def getchildren(self):
raise TypeError
def __str__(self):
return "点(%s, %s)" % (self.x, self.y)
class Line(Graphic):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def draw(self):
print(self)
def add(self, graphic):
raise TypeError
def getchildren(self):
raise TypeError
def __str__(self):
return "线段[%s, %s]" % (self.p1, self.p2)
class Picture(Graphic):
def __init__(self):
self.children = []
def add(self, graphic):
self.children.append(graphic)
def getchildren(self):
return self.children
def draw(self):
print("------复合图形------")
for g in self.children:
g.draw()
print("------END------")
pic1 = Picture()
point = Point(2,3)
pic1.add(point)
pic1.add(Line(Point(1,2), Point(4,5)))
pic1.add(Line(Point(0,1), Point(2,1)))
pic2 = Picture()
pic2.add(Point(-2,-1))
pic2.add(Line(Point(0,0), Point(1,1)))
pic = Picture()
pic.add(pic1)
pic.add(pic2)
pic.draw()
#pic1.draw()
#point.draw()
3. 装饰模式
内容:
装饰模式允许我们使用其它对象将提供核心功能的对象包装起来, 借此提供额外的功能或者修改原对象的行为和功能. 被装饰后的对象应与原对象拥有相同的接口, 这样允许我们在不影响代码其它部分的情况下自由选择使用装饰后的对象还是原对象. 装饰是可以嵌套的, 由在最内层的对象提供核心功能.
装饰模式与Python装饰器:
首先要明确一点, 装饰模式与Python中的装饰器是不同的概念.
本篇文章仅讨论装饰模式, 以后也会整理发布Python装饰器相关的文章供大家参考.
装饰模式是一种无关具体语言的设计模式, 而Python语言中的装饰器是一种可调用对象, 它接收一个函数作为参数, 也会返回一个函数作为结果. 一般会将返回的函数重新赋值代替原函数来扩展函数, 方法或类的功能.
Python中的函数也是对象, 在Python中使用装饰器装饰函数其实就是在应用装饰模式. Python开发者们经常会用到这种用法, 以至于Python为装饰器提供了@decorator
的语法糖来简化这种写法.
当对函数进行装饰时, 一般都是为了永久的扩展或修改函数的功能, 所以都是在函数定义时, 而不是在运行时决定是否使用装饰器. 使用语法糖的好处是可以很清晰的看出函数是被装饰过的. 语法糖仅能用于我们自己的代码, 在想要装饰第三方的代码时, 由于我们无法修改源码, 所以依然只能使用重新赋值的方式进行装饰.
适用场景:
在需要拓展一些通用功能时可以考虑使用装饰模式, 比如:
- 校验(数据, 权限)
- 缓存
- 记录日志
- 加密
- 程序排错
在Django中就提供了许多视图的装饰器, 其中就有一些可以用来校验访问用户的权限或者缓存视图提高处理响应的效率.
装饰模式与继承:
以下讨论适用于单继承以及多继承.
由于装饰模式也可以给对象添加额外的功能, 有时也可以用装饰模式来代替继承.
对比使用继承, 装饰模式主要有两大优势, 或者说只有满足以下两点中的条件时才应该优先选择装饰模式:
- 可以根据一些条件动态决定是否装饰扩展对象的功能.
- 同一功能可以拥有多个可选的扩展.
装饰模式示例代码:
下面这个简单的例子中, 根据
debug
以及log
条件的值, 最终的adder
对象的行为也会发生变化. 当两个值都为True
时, 最终的adder
对象是经过了两层装饰后的对象, 在调用add
方法时既会在控制台中打印, 也会写入日志文件.
假如我们只需要写入日志的额外功能, 即只需要
LogAdder
类, 那么LogAdder
类也可以使用继承Adder
类的方式来实现. 如上文中提到的, 当有多个可选扩展和/或需要动态决定是否使用扩展功能时, 使用装饰模式更加简便.
class Adder:
def add(self, a, b):
return a + b
class PrintAdder:
def __init__(self, adder):
self.adder = adder
def add(self, a, b):
res = self.adder.add(a, b)
print(f"{a} + {b} = {res}")
return res
class LogAdder:
def __init__(self, adder):
self.adder = adder
def add(self, a, b):
res = self.adder.add(a, b)
with open("adder.log", "w") as f:
f.write(f"{a} + {b} = {res}\n")
return res
if __name__ == "__main__":
debug = True
log = True
adder = Adder()
if debug:
adder = PrintAdder(adder)
if log:
adder = LogAdder(adder)
adder.add(1, 1)
print("from log: ", end="")
with open("adder.log", "r") as f:
print(f.read())
"""
1 + 1 = 2
from log: 1 + 1 = 2
"""
上面的例子主要为了展示装饰模式的思想, 在Python中可以使用装饰器更简单的完成上面的例子, 一般也更推荐使用那种方式.
4. 桥接模式
内容:
桥接模式与适配器模式看起来十分相似, 它们的区别在于: 适配器模式是用在已有代码上让它们一起工作, 而桥接模式用在开始写其它代码之前.
桥接模式可以让我们将抽象部分与具体实现部分分开, 用组合代替继承, 这样后续调整扩展抽象部分或实现部分时更简单.
Python中的桥接模式中主要有两类角色:
- 具体实现化角色: 实现在抽象化角色中调用的接口
- 抽象化角色: 内部包含具体实现化角色, 通过调用它实现的接口来实现业务逻辑
当有需要时也可以按照传统的桥接模式定义四类角色来实现(参考http://c.biancheng.net/view/1364.html):
- 抽象化(Abstraction)角色
- 扩展抽象化(Refined Abstraction)角色
- 实现化(Implementor)角色
- 具体实现化(Concrete Implementor)角色
上面的内容可能不太好理解, 看看示例代码就容易理解了.
适用场景:
想要在多个对象中共享实现时可以使用桥接模式.
桥接模式示例代码:
下面的代码中的IOPrinter
是抽象化角色, 它目前拥有两个功能: 从指定I/O读取全部数据或者预览I/O中的部分内容. 这两个功能的实现被所有具体实现化角色共享.
StdinReader
是具体实现化角色, 它内部实现了抽象化角色需要调用的read()
方法, 同时Python内置的文件对象中也有read()
方法, 所以也可以作为一种具体实现.
class IOPrinter:
def __init__(self, reader):
self.reader = reader
def print_all(self):
print(self.reader.read())
def preview(self):
print(self.reader.read()[:5], "...")
class StdinReader:
def read(self):
return input("stdin: ")
if __name__ == "__main__":
with open("file.txt", "r") as f:
printer = IOPrinter(f)
printer.print_all()
printer = IOPrinter(StdinReader())
printer.preview()
这段代码也展示了使用桥接模式带来的程序扩展性, 我们可以通过扩展抽象化角色添加新的功能, 比如增加倒序打印, 同时也可以通过创建新的具体实现化角色来添加新的功能, 比如从网络上或从数据库读取数据.
5. 代理模式 (Proxy)
内容:为其他对象提供一种代理以控制对这个对象的访问。
角色:
- 抽象实体(Subject)
- 实体(RealSubject)
- 代理(Proxy)
适用场景:
- 远程代理:为远程的对象提供代理
- 虚代理:根据需要创建很大的对象
- 保护代理:控制对原始对象的访问,用于对象有不同访问权限时
优点:
- 远程代理:可以隐藏对象位于远程地址空间的事实
- 虚代理:可以进行优化,例如根据要求创建对象
- 保护代理:允许在访问一个对象时有一些附加的内务处理
代理模式示例代码:
from abc import ABCMeta, abstractmethod
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
def set_content(self, content):
pass
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
print("读取%s文件内容" % filename)
f = open(filename)
self.__content = f.read()
f.close()
def get_content(self):
return self.__content
def set_content(self, content):
f = open(self.filename, 'w')
f.write(content)
self.__content = content
f.close()
# ---远程代理
class ProxyA(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename)
def get_content(self):
return self.subj.get_content()
def set_content(self, content):
return self.subj.set_content(content)
# ---虚代理
class ProxyB(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()
x = ProxyB('abc.txt')
# print(x.get_content())
# ---保护代理
class ProxyC(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename)
def get_content(self):
self.subj.get_content()
def set_content(self, content):
raise PermissionError
# filename = "abc.txt"
# username = input()
# if username!="alex":
# p = ProxyC(filename)
# else:
# p = ProxyA(filename)
#
# print(p.get_content())
6. MVC模式
内容:
MVC其实是一种架构模式而不是一种设计模式, 两者的区别就是前者的应用范围更加广泛. 因为这个模式太重要了, 又和设计模式有一定关联, 于是决定放在一起讨论.
MVC模式是软件设计中的SoC(Separation of Concerns)原则应用在面向对象编程模式中的一种体现. SoC原则大致可以理解为: 将程序分成不同的部分, 每个部分都都有自己明确的分工, 每个部分只关注负责自己的分工.
MVC模式将应用程序分成三大部分: 模型(model), 视图(view)和控制器(controller). 这个模式的名称也来源于这三大部分.
- 模型是这三部分中的核心, 它包含负责程序的状态, 数据和业务逻辑.
- 视图不负责处理数据, 它只负责对模型的展示(当有用户交互时也负责用户交互). 视图可以有着多种形式, 比如图形用户界面, 统计图表, 纯文本等.
- 控制器是连接模型和视图之间的桥梁, 模型和视图之间的交互都通过它完成.
使用控制器作为传话筒看起来有些冗余, 但它有着存在的必要: 使用控制器可以在不修改模型的情况下让它拥有多个视图. 一般而言, 想要做到这一点的话每个视图都应该有着自己的控制器.
应用MVC模式的程序的典型使用流程:
- 用户的一些操作(如点击按钮)触发了一个视图.
- 视图将用户的动作告知控制器.
- 控制器处理用户输入并与模型交互.
- 模型进行数据的校验, 执行相关的操作然后通知控制器该如何继续.
- 控制器将结果传递给视图, 视图根据结果和指示更新显示的信息和数据.
优点与应用:
优点:
- 模型与视图的分离可以让负责不同部分的开发者同时进行开发互不影响.
- 模型与视图耦合性低, 修改扩展已有的模型和视图时不会相互干扰. 添加新的视图也很简单.
- 每部分分工明确, 易于后期维护。
应用:
一般来讲我们不需要从零开始实现MVC架构而是直接使用已有的应用了MVC架构或其变种的框架.
MVC或者它的变种在框架中非常常见, 比如Django框架就使用了从MVC演变来的MTV(Model-Template-View)模式.
名称对照表:
传统MVC模式 | Django MTV模式 |
---|---|
model | model |
view | template |
controller | view |
Django的设计者认为视图(view)描述控制着什么(what)数据可以被用户看到, 所以他使用视图来命名负责这个功能的组件. 而模板(template)在Django中负责展示数据, 它决定数据是怎样(how)展示给用户.
从零开始实现MVC模式
当已有的框架都不能满足我们的需求时, 我们也可以自己实现MVC模式.
设计原则
当从零开始实现MVC时, 模型, 视图和控制器需要满足以下原则.
模型
- 独立于展示方式.
- 包含所有的校验逻辑以及业务逻辑.
- 可以访问程序中的数据(数据库, 文件等).
- 负责更新应用程序的状态.
视图
- 展示数据.
- 允许用户交互.
- 不包含任何数据校验以及业务逻辑. 只包含最低限度的其它逻辑, 比如模板语言中的循环和分支等.
- 不能直接获取程序中的数据.
- 不保存任何数据.
控制器
- 模型发生改变时负责更新视图.
- 视图与用户有交互时负责更新模型.
- 不包含任何数据校验以及业务逻辑.
- 不能直接获取程序中的数据.
- 不显示任何数据.
- 当需要时, 在在模型和视图之间传递数据之前处理数据.
当我们想要确认是否正确的实现了MVC模式时, 可以试着回答以下问题:
- 如果程序有图形界面的话, 可以给图形界面换皮肤吗? 换皮肤简单吗? 增加让用户可以在运行过程中给界面换皮肤的功能困难吗? 如果不是很简单的话, 那么MVC模式没有被正确的实现.
- 假如应用没有图形界面的话, 为它添加一个图形界面容易吗? 假如没必要添加图形界面, 那么添加其它形式的视图容易吗? 比如文件, 图片或图表. 假如无法做到只靠添加新的视图和控制器而不修改模型来实现, 那么证明有些地方的实现有问题.
三、行为模式
1. 责任链模式(Chain of Responsibility)
内容:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
角色:
- 抽象处理者(Handler)
- 具体处理者(ConcreteHandler)
- 客户端(Client)
例:
- 请假部门批准:leader—>部门经理—>总经理
- Javascript事件浮升机制
适用场景:
- 有多个对象可以处理一个请求,哪个对象处理由运行时决定
- 在不明确接收者的情况下,向多个对象中的一个提交一个请求
优点:
- 降低耦合度:一个对象无需知道是其他哪一个对象处理其请求
缺点:
- 请求不保证被接收:链的末端没有处理或链配置错误
请假流程:
from abc import ABCMeta, abstractmethod
class Handler(metaclass=ABCMeta):
@abstractmethod
def handle_leave(self, day):
pass
class GeneralManagerHandler(Handler):
def handle_leave(self, day):
if day < 10:
print("总经理批准%d天假" % day)
return True
else:
print("呵呵")
return False
class DepartmentManagerHandler(Handler):
def __init__(self):
self.successor = GeneralManagerHandler()
def handle_leave(self, day):
if day < 7:
print("部门经理批准%d天假" % day)
return True
else:
print("部门经理无权准假")
return self.successor.handle_leave(day)
class ProjectDirectorHandler(Handler):
def __init__(self):
self.successor = DepartmentManagerHandler()
def handle_leave(self, day):
if day < 3:
print("项目主管批准%d天假" % day)
return True
else:
print("项目主管无权准假")
return self.successor.handle_leave(day)
day = 11
h = ProjectDirectorHandler()
print(h.handle_leave(day))
模仿js事件处理
# --高级例子--模仿js事件处理
from abc import ABCMeta, abstractmethod
class Handler(metaclass=ABCMeta):
@abstractmethod
def add_event(self, func):
pass
@abstractmethod
def handle(self):
pass
class BodyHandler(Handler):
def __init__(self):
self.func = None
def add_event(self, func):
self.func = func
def handle(self):
if self.func:
return self.func()
else:
print("已到最后一级,无法处理")
class ElementHandler(Handler):
def __init__(self, successor):
self.func = None
self.successor = successor
def add_event(self, func):
self.func = func
def handle(self):
if self.func:
return self.func()
else:
return self.successor.handle()
# 客户端
#