设计模式

什么是设计模式

Christopher Alexander:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次地使用该方案而不必做重复劳动。”

每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。

GoF(Gang of Four)

设计模式四个基本要素:模式名称、问题、解决方案、效果

面向对象的三大特性:

  • 封装:把数据和函数包装在类里 类的边界限制了一些外界的访问
  • 继承:复用
  • 多态:多态语言
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 封装
class A:
def __init__(self, x):
self.__test = x # 私有变量

def gettest(self):
return self.__test

def settest(self, x):
self.__test = x

# 继承:复用
class B(A):
def __init__(self):
pass

# override 重写 覆写
def settest(self):
self.__test = x + 1

# 多态 python 是一种多态语言
# 一个函数的多种表现

# 重载 python 不支持
def test(x, y):
return x+y

def test(x, y, z):
return x+y+z

test(2,3)
test(2,3,4)

接口

一种特殊的类(抽象类),声明了若干方法,要求继承该接口的类必须实现这些方法。目的就是对外保持一致

作用:限制继承接口的类的方法的名称及调用方式;隐藏了类的内部实现。

接口就是一种抽象的基类(父类),限制继承它的类必须实现接口中定义的某些方法

其实就是限制程序员的东西, 不能乱写, 按照定的规范去写

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 接口的两种写法
# 1.
class A:
def test(self):
raise NotImplementedError # 限制 子类必须实现


class B(A):
def test(self):
print('B.test')
# 2. 抽象类不能实例化
from abs import bastractmethod,ABCMeta

class A(metaclass=ABCMeta): # 抽象类
@abstractmethod
def test(self): # 抽象方法 必须在子类实现
raise NotImplementedError # 限制 子类必须实现

class B(A):
pass


class C(A):
def test(self):
print('B.test')

a = B() # 不能实例化
b = C() # 可以实例化

设计模式六大原则

开闭原则

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

里氏(Liskov)替换原则

所有引用基类(父类)的地方必须能透明地使用其子类的对象。功能保持一致

依赖倒置原则

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口(抽象)编程,而不是针对实现(实例)编程。

接口隔离原则

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。(多继承)示例

迪米特法则

一个软件实体应当尽可能少地与其他实体发生相互作用。解耦

五大原则没有这个迪米特法则

单一职责原则

不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 一个类(class)只干一件事

  • 合成复用原则

尽量使用合成/聚合的方式,而不是继承。示例

合成 复用 继承 的使用分情况而定

设计模式

创建型模式

工厂方法模式

定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:

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

工厂方法模式相比简单工厂模式将每个具体产品都对应了一个具体工厂。

示例

工厂方法模式

适用场景:

  • 需要生产多种、大量复杂对象的时候
  • 需要降低耦合度的时候
  • 当系统中的产品种类需要经常扩展的时候

优点:

  • 每个具体产品都对应一个具体工厂类,不需要修改工厂类代码
  • 隐藏了对象创建的实现细节

缺点:

  • 每增加一个具体产品类,就必须增加一个相应的具体工厂类

简单工厂模式

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

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

优点:

  • 隐藏了对象创建的实现细节
  • 客户端不需要修改代码

缺点:
违反了单一职责原则,将创建逻辑集中到一个工厂类里
当添加新产品时,需要修改工厂类代码,违反了开闭原则

示例

单例模式

比如: 数据库连接

好的单列模式会写一个基类(示例

单例(Singleton): 保证一个类只有一个实例,并提供一个访问它的全局访问点。

适用场景:

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时

优点:

  • 对唯一实例的受控访问
  • 单例相当于全局变量,但防止了命名空间被污染

与单例模式功能相似的概念:全局变量、静态变量(方法)

1
2
3
4
5
6
7
8
9
10
11
class A:
test = 0

A.test = 1

a = A()
print(a.test)
# a.test = 1 # 对象变量的修改不会影响到类的静态变量

b = A()
print(b.test)

抽象工厂模式

定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
例:生产一部手机,需要手机壳CPU操作系统三类对象进行组装,其中每类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。

角色:

示例

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

相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品。

抽象工厂模式

适用场景:

  • 系统要独立于产品的创建与组合时
  • 强调一系列相关的产品对象的设计以便进行联合使用时
  • 提供一个产品类库,想隐藏产品的具体实现时

优点:

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

缺点:

  • 难以支持新种类的(抽象)产品

建造者模式

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

角色:

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

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

建造者模式

示例

适用场景:

  • 当创建复杂对象的算法(Director)应该独立于该对象的组成部分(Builder)时
  • 当构造过程允许被构造的对象有不同的表示时(不同Builder)。

优点:

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

小结

小结

使用 Abstract Factory(抽象工厂)、Prototype(原型模式) 或 Builder(建造者) 的设计甚至比使用 Factory Method(工厂方法) 的那些设计更灵活,但它们也更加复杂。通常,设计以使用 Factory Method(简单工厂也可以) 开始,并且当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。当你在设计标准之间进行权衡的时候,了解多个模式可以给你提供更多的选择余地。

也就是说,不用一开始就选好模式,先从简单的模式开始,如果需要频繁的改代码,就用工厂方法,等等…… 一步一步递进

依赖于继承的创建型模式:工厂方法模式
依赖于组合的创建性模式:抽象工厂模式、创建者模式

结构型模式

适配器模式

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

  • 目标接口(Target)
  • 待适配的类(Adaptee)
  • 适配器(Adapter)(套壳)

两种实现方式:

  • 类适配器:使用多继承
  • 对象适配器:使用组合

示例

适配器模式

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

类适配器和对象适配器有不同的权衡。

类适配器

  • 用一个具体的 Adapter 类对 Adaptee 和 Target 进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类 Adapter 将不能胜任工作。
  • 使得 Adapter 可以重定义 Adaptee 的部分行为,因为 Adapter 是 Adaptee 的一个子类。
  • 仅仅引入(继承)了一个对象,并不需要额外的指针以间接得到 adaptee。

对象适配器则

  • 允许一个 Adapter 与多个 Adaptee-即 Adaptee 本身以及它的所有子类(如果有子类的话)一同时工作。Adapter 也可以一次给所有的 Adaptee 添加功能。
  • 使得重定义 Adaptee 的行为比较困难。这就需要生成 Adaptee 的子类并且使得 Adapter 引用这个子类而不是引用 Adaptee 本身。

组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

二叉树的结构
角色:

  • 抽象组件(Component)
  • 叶子组件(Leaf)
  • 复合组件(Composite)
  • 客户端(Client)

示例

组合模式

适用场景:

  • 表示对象的“部分-整体”层次结构(特别是结构是递归的)
  • 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象

优点:

  • 定义了包含基本对象和组合对象的类层次结构
  • 简化客户端代码,即客户端可以一致地使用组合对象和单个对象
  • 更容易增加新类型的组件

缺点:

  • 很难限制组合中的组件

代理模式

为其他对象提供一种代理以控制对这个对象的访问。
角色:

  • 抽象实体(Subject)
  • 实体(RealSubject)
  • 代理(Proxy)

示例

适用场景:

  • 远程代理:为远程的对象提供代理
  • 虚代理:根据需要创建很大的对象
  • 保护代理:控制对原始对象的访问,用于对象有不同访问权限时

优点:

  • 远程代理:可以隐藏对象位于远程地址空间的事实
  • 虚代理:可以进行优化,例如根据要求创建对象
  • 保护代理:允许在访问一个对象时有一些附加的内务处理

代理模式

行为型模式

责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

角色:

  • 抽象处理者(Handler)
  • 具体处理者(ConcreteHandler)
  • 客户端(Client)

示例

例:
请假部门批准:leader -> 部门经理 -> 总经理
Javascript事件浮升机制

责任链模式

适用场景:

  • 有多个对象可以处理一个请求,哪个对象处理由运行时决定
  • 在不明确接收者的情况下,向多个对象中的一个提交一个请求

优点:

  • 降低耦合度:一个对象无需知道是其他哪一个对象处理其请求

缺点:

  • 请求不保证被接收:链的末端没有处理或链配置错误

迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

实现方法:__iter____next__

示例

适用于封装数据结构,这种结构类似与列表,或树,封装数据类型,不让外人知道是怎么存的

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。观察者模式又称“发布-订阅”模式

角色:

  • 抽象主题(Subject)
  • 具体主题(ConcreteSubject)——发布者
  • 抽象观察者(Observer)
  • 具体观察者(ConcreteObserver)——订阅者

示例

观察者模式

适用场景:

  • 当一个抽象模型有两方面,其中一个方面依赖于另一个方面。将这两者封装在独立对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。

优点:

  • 目标和观察者之间的耦合最小
  • 支持广播通信

缺点:

  • 多个观察者之间互不知道对方存在,因此一个观察者对主题的修改可能造成错误的更新。

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

角色:

  • 抽象策略(Strategy)
  • 具体策略(ConcreteStrategy)
  • 上下文(Context)

示例

适用场景:

  • 许多相关的类仅仅是行为有异
  • 需要使用一个算法的不同变体
  • 算法使用了客户端无需知道的数据
  • 一个类中的多种行为以多个条件语句的形式存在,可以将这些行为封装如不同的策略类中。

策略模式

优点:

  • 定义了一系列可重用的算法和行为
  • 消除了一些条件语句
  • 可以提供相同行为的不同实现

缺点:

  • 客户必须了解不同的策略
  • 策略与上下文之间的通信开销
  • 增加了对象的数目

模板方法模式

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

角色:

  • 抽象类(AbstractClass):定义抽象的原子操作(钩子操作);实现一个模板方法作为算法的骨架。
  • 具体类(ConcreteClass):实现原子操作

示例

模板方法模式

适用场景:

  • 一次性实现一个算法的不变的部分
  • 各个子类中的公共行为应该被提取出来并集中到一个公共父类中以避免代码重复
  • 控制子类扩展

总结

设计模式可互相嵌套, 如: 策略模式的算法写成单例模式比较好;

  • 创建型模式都是对象怎么创建

  • 结构型模式是怎么把类组织在一起

    1. 适配器模式类写坏了, 和其他类不适配
    2. 组合模式是几个类怎么表现一样, 叶子节点和复合节点怎么表现一样
    3. 代理模式提供几种代理, 以控制加权限, 或加内容
  • 行为型模式是怎么做事, 方法函数怎么做

    1. 责任链模式, 一个一个传下去
    2. 迭代器模式, 一个一个拿元素处理 (应用面狭小)
    3. 观察者模式, 一个一个更新

加深理解

工厂模式是什么?有什么用?怎么用?什么好处?

哪个工厂模式,简单工厂模式,工厂方法模式还是抽象工厂模式。

简单工厂模式就是把所有产品的创建细节都隐藏在一个工厂里,也就是把要创建的这个类的对象的创建细节隐藏在工厂里,这就叫简单工厂。

问题:因为这个类所有的产品创建细节都隐藏在一个工厂里,那如果要加产品,就需要改工厂的代码,这个就不符合开闭原则。简单工厂的一个类,承载了很多产品的创建,所以不符合单一职责原则。就需要从简单工厂升级到工厂方法模式。


工厂方法模式就是是一个产品,一种产品的创建过程,它隐藏在一个单独的工厂里,每一个产品对应一个工厂,同样, 产品的创建过程隐藏在这个工厂里,

需要的注意的是:有多个工厂的时候,我们需要一个工厂的接口抽象工厂也就是工厂方法模式。工厂方法模式还是把对象的创建过程隐藏在了工厂里。它和之前简单工厂相比的话,一个产品对应一个工厂,加新产品的话,只需要再加一个工厂就可以了,不需要修改工厂代码。缺点就是加一个产品需要添加两类。类写的比较多,这是工厂方法模式。


抽象工厂模式和跟前面的两个就不太一样,它是生产一个产品系列,或者叫一套产品,生产一套产品的时候,一个工厂负责生产一套。
好处:

  • 第一,把对象的创建细节隐藏在工厂里,
  • 第二, 可以保持产品系列的一致性,也就是加约束。

比如:苹果的IOS只能加苹果的手机壳,苹果CPU, 这就叫一致性,小产品之间的约束。

设计模式大全

  • 创建型模式:
    1. 工厂方法模式
    2. 抽象工厂模式
    3. 创建者模式
    4. 原型模式
    5. 单例模式
  • 结构型模式
    1. 适配器模式
    2. 桥模式
    3. 组合模式
    4. 装饰模式
    5. 外观模式
    6. 享元模式
    7. 代理模式
  • 行为型模式
    1. 解释器模式
    2. 责任链模式
    3. 命令模式
    4. 迭代器模式
    5. 中介者模式
    6. 备忘录模式
    7. 观察者模式
    8. 状态模式
    9. 策略模式
    10. 访问者模式
    11. 模板方法模式