设计模式及python实现
Christopher Alexander:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次地使用该方案而不必做重复劳动。”
设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。最为重要的是,设计模式也是一把双刃剑,如果设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。
起初,你会认为“模式”就是为了解决一类特定问题而特别想出来的明智之举。说的没错,看起来的确是通过很多人一起工作,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案。也许这些问题你曾经见过或是曾经解决过,但是你的解决方案很可能没有模式这么完备。
虽然被称为“设计模式”,但是它们同“设计“领域并非紧密联系。设计模式同传统意义上的分析、设计与实现不同,事实上设计模式将一个完整的理念根植于程序中,所以它可能出现在分析阶段或是更高层的设计阶段。很有趣的是因为设计模式的具体体现是程序代码,因此可能会让你认为它不会在具体实现阶段之前出现(事实上在进入具体实现阶段之前你都没有意识到正在使用具体的设计模式)。
可以通过程序设计的基本概念来理解模式:增加一个抽象层。抽象一个事物就是隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来。当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考那些不会变动的设计方法了。这么做不仅会使代码可维护性更高,而且会让代码更易于理解,从而降低开发成本。
三种最基本的设计模式:
创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。
0|1设计模式六大原则
开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
里氏(Liskov)替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。
单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
0|1接口
接口:一种特殊的类,声明了若干方法,要求继承该接口的类必须实现这些方法。
作用:限制继承接口的类的方法的名称及调用方式;隐藏了类的内部实现。
接口就是一种抽象的基类(父类),限制继承它的类必须实现接口中定义的某些方法。
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()
报错定义接口
0|1创建型模式
- 简单工厂模式
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。
角色:
工厂角色(Creator)
抽象产品角色(Product)
具体产品角色(Concrete Product)
优点:
隐藏了对象创建的实现细节
客户端不需要修改代码
缺点:
违反了单一职责原则,将创建逻辑几种到一个工厂类里
当添加新产品时,需要修改工厂类代码,违反了开闭原则
PaymentFactory简单工厂
- 工厂方法模式(Factory Method)
内容:定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:
抽象工厂角色(Creator)
具体工厂角色(Concrete Creator)
抽象产品角色(Product)
具体产品角色(Concrete Product)
工厂方法模式相比简单工厂模式将每个具体产品都对应了一个具体工厂。
适用场景:
需要生产多种、大量复杂对象的时候。
需要降低耦合度的时候。
当系统中的产品种类需要经常扩展的时候。
优点:
每个具体产品都对应一个具体工厂类,不需要修改工厂类代码
隐藏了对象创建的实现细节
缺点:
每增加一个具体产品类,就必须增加一个相应的具体工厂类
工厂方法
- 抽象工厂方法(Abstract Factory)
内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
例:生产一部手机,需要手机壳、CPU、操作系统三类对象进行组装,其中每类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。
角色:
抽象工厂角色(Creator)
具体工厂角色(Concrete Creator)
抽象产品角色(Product)
具体产品角色(Concrete Product)
客户端(Client)
相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品。
适用场景:
系统要独立于产品的创建与组合时
强调一系列相关的产品对象的设计以便进行联合使用时
提供一个产品类库,想隐藏产品的具体实现时
优点:
将客户端与类的具体实现相分离
每个工厂创建了一个完整的产品系列,使得易于交换产品系列
有利于产品的一致性(即产品之间的约束关系)
缺点:
难以支持新种类的(抽象)产品
抽象工厂
- 建造者模式(Builder)
内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色:
抽象建造者(Builder)
具体建造者(Concrete Builder)
指挥者(Director)
产品(Product)
建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。
适用场景:
当创建复杂对象的算法(Director)应该独立于该对象的组成部分以及它们的装配方式(Builder)时
当构造过程允许被构造的对象有不同的表示时(不同Builder)。
优点:
隐藏了一个产品的内部结构和装配过程
将构造代码与表示代码分开
可以对构造过程进行更精细的控制
建造者模式
- 单例模式(Singleton)
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
角色:
单例(Singleton)
适用场景
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
优点:
对唯一实例的受控访问
单例相当于全局变量,但防止了命名空间被污染。
与单例模式功能相似的概念:全局变量、静态变量(方法)
实现:
- 使用__new__方法
- 装饰器方法
- import方法
- 使用metaclass
- 原型模式(Prototype)
内容:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
使用场景:
通过动态装载;
为了避免创建一个与产品类层次平行的工厂类层次时;
当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
原型模式
创建型模式总结
使用抽象工厂(Abstract Factory)、原型(Prototype)或者建造者(Builder)的设计甚至比工厂方法(Factory Method)的那些设计更灵活,但它们也更加复杂。通常,设计以使用工厂方法(Factory Method)开始。并且当设计者发现需要更大的灵活性时,设计便会想其他创建模式烟花。当你在设计标准之间权衡的时候,了解多个模式可以给你提供给更多的选择余地。
依赖于继承的创建型模式:工厂方法模式
依赖于组合的创建型模式:抽象工厂模式、创建者模式
0|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本身。
适配器
- 组合模式(Composite)
内容:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
角色:
抽象组件(Component)
叶子组件(Leaf)
复合组件(Composite)
客户端(Client)
适用场景:
表示对象的“部分-整体”层次结构(特别是结构是递归的)
希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象
优点:
定义了包含基本对象和组合对象的类层次结构
简化客户端代码,即客户端可以一致地使用组合对象和单个对象
更容易增加新类型的组件
缺点:
很难限制组合中的组件
组合
- 代理模式 (Proxy)
内容:为其他对象提供一种代理以控制对这个对象的访问。
角色:
抽象实体(Subject)
实体(RealSubject)
代理(Proxy)
适用场景:
远程代理:为远程的对象提供代理
虚代理:根据需要创建很大的对象
保护代理:控制对原始对象的访问,用于对象有不同访问权限时
优点:
远程代理:可以隐藏对象位于远程地址空间的事实
虚代理:可以进行优化,例如根据要求创建对象
保护代理:允许在访问一个对象时有一些附加的内务处理
代理模式
0|1行为模式
- 责任链模式(Chain of Responsibility)
内容:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
角色:
抽象处理者(Handler)
具体处理者(ConcreteHandler)
客户端(Client)
例:
请假部门批准:leader—>部门经理—>总经理
Javascript事件浮升机制
适用场景:
有多个对象可以处理一个请求,哪个对象处理由运行时决定
在不明确接收者的情况下,向多个对象中的一个提交一个请求
优点:
降低耦合度:一个对象无需知道是其他哪一个对象处理其请求
缺点:
请求不保证被接收:链的末端没有处理或链配置错误
请假流程
模仿js事件处理
- 迭代器模式(Iterator)
内容:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
适用场景:
访问一个聚合对象的内容而无需暴露它的内部表示。
支持对聚合对象的多种遍历。
为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)
实现方法:__iter__、__next__
链表
- 观察者模式(Observer)
内容:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。观察者模式又称“发布-订阅”模式
角色:
抽象主题(Subject)
具体主题(ConcreteSubject)——发布者
抽象观察者(Observer)
具体观察者(ConcreteObserver)——订阅者
适用场景:
当一个抽象模型有两方面,其中一个方面依赖于另一个方面。将这两者封装在独立对象中以使它们可以各自独立地改变和复用。
当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
优点:
目标和观察者之间的抽象耦合最小
支持广播通信
缺点:
多个观察者之间互不知道对方存在,因此一个观察者对主题的修改可能造成错误的更新。
发布者——订阅者
- 策略模式(Strategy)
内容:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
角色:
抽象策略(Strategy)
具体策略(ConcreteStrategy)
上下文(Context)
适用场景:
许多相关的类仅仅是行为有异
需要使用一个算法的不同变体
算法使用了客户端无需知道的数据
一个类中的多种行为以多个条件语句的形式存在,可以将这些行为封装如不同的策略类中。
优点:
定义了一系列可重用的算法和行为
消除了一些条件语句
可以提供相同行为的不同实现
缺点:
客户必须了解不同的策略
策略与上下文之间的通信开销
增加了对象的数目
策略模式
- 模板方法(Template Method)
内容:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
角色:
抽象类(AbstractClass):定义抽象的原子操作(钩子操作);实现一个模板方法作为算法的骨架。
具体类(ConcreteClass):实现原子操作
适用场景:
一次性实现一个算法的不变的部分
各个子类中的公共行为应该被提取出来并集中到一个公共父类中以避免代码重复
控制子类扩展
模板方法
0|1参考文献:
《二十三种设计模式及其python实现》——李琼羽
EOF
作 者:Taosiyu
出 处:https://www.cnblogs.com/taosiyu/p/11293949.html