设计模式笔记

总结自 Leetcode深入浅出设计模式

其他优秀参考资料:

设计模式(菜鸟教程)

python-patterns

图说设计模式

前言

本文是浓缩笔记, 方便日后回忆, 且主要以Python语言视角去考虑, 具体学习请参考上述链接中的内容

构建型模式主要讲述一些创建类时的模式

结构型模式主要讲述如何将类或者对象结合在一起形成更大的结构

行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用

六大原则(Six principles)

  • 开闭原则:一个软件实体如类、模块和函数应该对修改封闭,对扩展开放。

  • 单一职责原则:一个类只做一件事,一个类应该只有一个引起它修改的原因。

  • 里氏替换原则:子类应该可以完全替换父类。也就是说在使用继承时,只扩展新功能,而不要破坏父类原有的功能。

  • 依赖倒置原则:细节应该依赖于抽象,抽象不应依赖于细节。把抽象层放在程序设计的高层,并保持稳定,程序的细节变化由低层的实现层来完成。

  • 迪米特法则:又名“最少知道原则”,一个类不应知道自己操作的类的细节,换言之,只和朋友谈话,不和朋友的朋友谈话。

  • 接口隔离原则:客户端不应依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户(client)将会只需要知道他们感兴趣的方法。

构建型模式(Creational Patterns)

简单工厂模式

多个类需要初始化时, 不对每个类单独初始化, 而是再用一个大的工厂类包装, 当需要哪个类的实例时, 就调用工厂类的实例生成相应类的实例

工厂模式(Factory)

每个工厂类用方法对每个类的实例化进行包装(一一对应), 需要哪个类的实例就调用哪个类的工厂类实例进行生产

抽象工厂模式(Abstract factory)

对所有工厂类再进行一层抽象

抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。因为在工厂里增加新方法就对所以已经实现工厂的类都有影响。

单例模式(Singleton)

单例模式非常常见,某个对象全局只需要一个实例时,就可以使用单例模式

使用类的静态方法返回实例, 有以下两种方法

饿汉式:变量在声明时便初始化

懒汉式:先声明一个空变量,需要用时才初始化(按需加载,避免了内存浪费)

懒汉式需要注意线程安全

建造者模式(Builder)

定义: 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示

建造者模式用于创建过程稳定,但配置多变的对象, 好处是不用担心忘了指定某个配置,保证了构建过程是稳定的

可以定义多个Builder对应不同的配置(减少一大串的传参)

原型模式(Prototype)

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

其实就是对类实现一个clone()方法, 这样当我们想要与原来的类的实例一模一样(各种内部变量一样)的实例的时候, 直接调用clone()方法即可

小结

  • 工厂方法模式:为每一类对象建立工厂,将对象交由工厂创建,客户端只和工厂打交道

  • 抽象工厂模式:为每一类工厂提取出抽象接口,使得新增工厂、替换工厂变得非常容易

  • 单例模式:全局使用同一个对象,分为饿汉式和懒汉式。懒汉式有双检锁和内部类两种实现方式

  • 建造者模式:用于创建构造过程稳定的对象,不同的 Builder 可以定义不同的配置

  • 原型模式:为一个类定义 clone 方法,使得创建相同的对象更方便

结构型模式(Structural Patterns)

适配器模式(Adapter)

定义: 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作

桥接模式(Bridge)

定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式或接口模式

有些易变的类(颜色, 形状)没必要搞继承, 可以将易变的类实例化(定义的”实现部分”)后赋值给其他类(定义的”抽象部分”)的实例变量

组合模式(Composite)

定义:又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构

相似的类搞成树结构

又有两种子方式: 透明方式与安全方式.

透明方式枝节点声明所有管理子对象的方法, 违背了接口隔离原则(即全部继承接口方法)。但是可以统一接口调用

安全方式枝节点只声明必要方法, 叶节点按需声明自己所需方法

个人觉得Python应该用安全方式, 因为Python不用区分是叶子结点还是枝节点

装饰模式(Decorator)

定义: 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活

用类对其他类的实例进行包装, 有以下两种方式

  • 透明装饰模式: 用于增强功能的装饰模式, 仅用于增强功能,并不会改变原有类的功能, 可以无限装饰

  • 半透明装饰模式: 没有修改原有的功能,扩展了新的功能, 无法多次装饰

外观模式(Facade)

定义: 外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

即封装

享元模式(Flyweight)

定义: 运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式

享元模式,就是用个map存对象。不生成新的重复的对象给别人,从map中找以前的给别人。和单例的差别是,单例只维护一个对象,享元维护n个对象。

代理模式(Proxy)

定义: 给某一个对象提供一个代理,并由代理对象控制对原对象的引用

静态代理和装饰模式一模一样, 不同在于装饰模式是为了增强功能或添加功能,代理模式主要是为了加以控制

动态代理与静态代理的原理一模一样, 只是使用了反射技术节省代码量

小结

  • 适配器模式:用于有相关性但不兼容的接口

  • 桥接模式:用于同等级的接口互相组合

  • 组合模式:用于整体与部分的结构

  • 外观模式:体现封装的思想

  • 享元模式:体现面向对象的可复用性

  • 代理模式:主要用于对某个对象加以控制

行为型模式(Behavioral Patterns)

责任链模式(Chain of responsibility)

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

责任链主要用于处理职责相同,程度不同的类

命令模式(Command)

定义: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作

本质是封装”方法调用”: 原先是一个类管理使用好几个类, 命令模式中对被调用类的方法使用新的类进行了进一步封装

优点: 降低系统的耦合度, 扩展性强, 封装”方法调用”,方便实现 Undo 和 Redo 操作, 灵活性强,可以实现宏命令

缺点: 会产生大量命令类。增加了系统的复杂性

解释器模式(Interpreter)

定义: 给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子

用类的方式解析语言的文法

迭代器模式(Iterator)

定义: 提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节

类提供next和hasNext方法在不暴露内部数据的情况下供他人依次访问各个元素

中介者模式(Mediator)

定义: 定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互

中介者模式的缺点也很明显:由于它将所有的职责都移到了中介者类中,也就是说中介类需要处理所有类之间的协调工作,这可能会使中介者演变成一个超级类。所以使用中介者模式时需要权衡利弊。

备忘录模式(Memento)

定义: 在不破坏封装的条件下,通过备忘录对象存储另外一个对象内部状态的快照,在将来合适的时候把这个对象还原到存储起来的状态

观察者模式(Observer)

定义: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

对被观察对象的父类实现一个方法, 此方法会在指定条件下被调用并通知所以已绑定的观察者

状态模式(State)

定义: 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

将与特定状态相关的行为封装到一个状态对象中, 当状态改变时, 内部变量绑定的对象也变了, 从而改变其行为(调用绑定对象的地方对于绑定对象的改变是不知道的也无需知道, 因为调用的方式不变)

策略模式(Strategy)

定义: 定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化

通过结合工厂模式, 传入参数改变对象内部策略的选择和使用

与状态模式模式很像,对比如下

*使用策略模式时,程序只需选择一种策略就可以完成某件事。也就是说每个策略类都是完整的,都能独立完成这件事情,如上文所言,强调的是殊途同归

*使用状态模式时,程序需要在不同的状态下不断切换才能完成某件事,每个状态类只能完成这件事的一部分,需要所有的状态类组合起来才能完整的完成这件事,强调的是随势而动

模板方法模式(Template method)

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

被继承的父类定义大体的逻辑过程, 其中一些步骤由子类具体实现

官方例子蛮好的, 直接抄过来

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
33
34
35
36
37
38
39
40
41
// 模板父类
abstract class LeaveRequest {

void request() {
System.out.print("本人");
System.out.print(name());
System.out.print("因");
System.out.print(reason());
System.out.print("需请假");
System.out.print(duration());
System.out.print("天,望批准");
}

abstract String name();

abstract String reason();

abstract String duration();
}

// 子类实现细节
class MyLeaveRequest extends LeaveRequest {
@Override
String name() {
return "阿笠";
}

@Override
String reason() {
return "参加力扣周赛";
}

@Override
String duration() {
return "0.5";
}
}

// 调用
// 输出:本人阿笠因参加力扣周赛需请假0.5天,望批准
new MyLeaveRequest().request();

访问者模式(Visitor)

定义: 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

在访问者中定义如何处理某对象结构中的各元素, 对于定义后半句, 新操作则可以用新的访问者类实现

小结

  • 责任链模式:处理职责相同,程度不同的对象,使其在一条链上传递

  • 命令模式:封装“方法调用”,将行为请求者和行为实现者解耦

  • 解释器模式:定义自己的语法规则

  • 迭代器模式:定义 next() 方法和 hasNext() 方法,让外部类使用这两个方法来遍历列表,以达到隐藏列表内部细节的目的

  • 中介者模式:通过引入中介者,将网状耦合结构变成星型结构

  • 备忘录模式:存储对象的状态,以便恢复

  • 观察者模式:处理一对多的依赖关系,被观察的对象改变时,多个观察者都能收到通知

  • 状态模式:关于多态的设计模式,每个状态类处理对象的一种状态

  • 策略模式:殊途同归,用多种方法做同一件事

  • 模板方法模式:关于继承的设计模式,父类是子类的模板

  • 访问者模式:将数据的结构和对数据的操作分离