为什么我们需要设计模式
有一类问题会在软件设计中反复出现,我们能够提出一种抽象的方法来解决这类问题,这就是设计模式。
设计模式的七大原则
- 单一职责原则
- 接口隔离原则
- 依赖反转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
23种设计模式
5个创建型
- 单例模式
- 工厂模式
- 抽象工厂模式
- 原型模式
- 建造者模式
7个结构性
- 适配器模式
- 桥接模式
- 装饰者模式
- 组合模式
- 外观模式
- 享元模型
- 代理模式
11个行为型
- 模版方法模式
- 命令模式
- 访问者模式
- 迭代器模式
- 观察者模式
- 中介者模式
- 备忘录模式
- 解释器模式
- 状态模式
- 策略模式
- 责任链模式
单一职责原则
一个类只管一个职责。
例子1
如果我们创建了交通工具类,他掌管着很多工具,汽车、飞机、轮船,显然我们不适合让一个类来管理这么多种交通工具,这样导致职责太多,也不适合分别为这三种交通工具建立3个类,这样导致修改过多,正确的做法是创建三个函数,来分别管理他们。
例子2
又如我们有树、链表、数组,我们要便利他们,你肯定不适合创建一个类,一个函数来遍历。应该是一个类三个函数分别遍历树、链表、数组
但是如果这种方法级的单一职责原则导致类过于庞大,应该考虑到使用类级的单一职责原则。
这样可以降低类的复杂度,提高可读性,降低变更的风险。
接口隔离原则
将类之间的依赖降低到最小的接口处。
例子1
接口interface有5个方法,被类B和类D实现,被类A和类C依赖,但是A使用B只依赖接口123,C使用D只依赖接口145,这就导致了你的B多实现了4、5两个方法,D多实现了2、3两个方法。我们应该把interface拆分成3个,1,23,45,B实现1和23,D实现1和45。
例子2
比方说你有一个数组类和一个链表类,都实现了一个接口类,这个接口包含插入、删除、遍历、反转、排序,然后你有一个数组操作类,他只用到了插入删除遍历排序,还有一个链表操作类,他只用到了插入删除遍历反转,这个设计就很糟糕。
你应该创建3个接口,第一个为插入删除遍历,第二个为反转,第三个为排序,让数组实现第一个接口和最后一个接口,让链表实现第一个接口和第二个接口。
依赖反转原则
高层模块不应该依赖底层模块,他们都应该依赖其抽象,抽象不应该依赖具体,具体应该依赖抽象,因为具体是多变的,抽象是稳定的。
例子1
有一个email类,一个person类,person接受邮件的时候要将email作为函数参数来实现,这就导致person依赖email,这很糟糕,万一需求变了,来了个微信类,来了个QQ类,来了个钉钉类,你岂不是要实现一堆person的方法?
你应该设计一个接受者接口,让其作为接受者接口的实现,让person依赖接受者这个接口。
例子2
有一个数组类和一个操作类,操作类需要操作数组的首个元素,我们将数组类作为操作类的函数的参数,这很糟糕,万一需求变了,要求操作链表怎么办?
我们应该定义一个容器接口,让数组类实现它,对于操作类,我们只需要将容器接口作为参数即可,如果需求变化,加入了链表类,也不会导致大量的修改,非常稳定。
里氏替换原则
子类能够替换父类,并不产生故障,子类不要重写父类的方法。如果不能满足就不要这样继承。
例子1
做了一个减法类,然后让另外一个加法类继承减法类,重写了减法类的方法为加法。你觉得这样合适吗?你应该定一个更加基础的类,让加法类和减法类都继承它。
例子2
做了一长方形类,有个函数叫返回面积,让正方形类继承了长方形类,有个主类先定义了长为2,宽为2的长方形,然后让长扩大4倍,就成了82,如果你用正方形类来替换长方形类的位置,扩大4被以后面积成了88,这很糟糕,应该让长方形继承正方形。
开闭原则
一个模块和函数应该对扩展开放,对修改关闭。
就是说我们尽量去扩展原有的功能,而不是修改功能。另一方面源代码应该允许扩展。
例子
有一个数组、链表,有一个排序类,我们让排序类去对数组和链表排序,这个也不是很好,如果我们加入了双端数组,则需要修改排序类。
正确的方法是将排序作为一个成员方法来实现,即在基类中就定义一个排序的虚函数。
迪米特法则
一个类和其他类个关系越少越好。
例子
有类A,B,C,其中B是A的成员,C是B的成员,下面是这个糟糕的例子
1 | class C { |
这里注意到b.c.f();这里导致了A和C扯上了关系,正确的做法应该是在B中声明函数cf();
1 | class C { |
现在A就和C没关系了。
合成复用原则
尽量使用合成而不是继承。
说的就是让一个类的对象做另外一个类的成员变量。
单例模式
单例模式的类,只允许出现一个对象。
饿汉式
构造函数私有化,在内部之间final,new创建自己或者使用静态代码块new,提供静态方法访问。
简单,避免线程同步,在类装载的时候实例化,没有达到懒加载,可能造成内存浪费。
线程不安全的懒汉式
构造函数私有化,在内部创建自己的引用,设为空值,提供静态方法调用,在静态方法中有选择性地new自己。
简单,线程不安全,达到了懒加载效果。
线程安全的懒汉式
在if中进行同步操作,在同步中进行if最后new,注意使用volatile。
简单,线程安全。
静态内部类
字面意思,很强,懒加载,因为类只会加载一次,所以线程安全,这个写法最优秀。
枚举方式
用枚举类型将类导入。
工厂模式
设计一个工厂类,包含一个函数,返回指定类型
工厂方法模式
我们让原先的工厂作为基类,让多个工厂继承他,这就成为了工厂方法模式,比方说最开始的时候我们有多种口味的🍕,我们使用工厂模式完成了,现在来了新的需求,要求有多个地方的🍕,这时候我们就使用继承。为每个地理位置创建一个工厂。
抽象工厂模式
考虑工厂方法模式,让工厂的父类作为接口即可。
原型模式
用原型实例来拷贝另一个对象,java中为.clone(),c++中为=。
深拷贝前拷贝
是否拷贝指针指向的内容。
建造者模式
将复杂对象的建造方式抽象出来,一步一步抽象的建造出来。
产品
创建的产品对象。
抽象建造者
指定建造流程。
具体建造者
实现抽象建造者。
指挥者
隔离客户和对象的生产过程,控制产品对象的生产过程。
建房子
比方你现在要建造一个房子,你需要打地基,砌墙,封顶,你可以建造矮房子,也可以建造高房子,现在你就可以使用建造者模式,房子是产品,建造者能打地基,砌墙,封顶,房子组合为建造者,建造者聚合指挥者,我们依赖指挥者。
StringBuilder
Appendable是抽象建造者,AbstractStringBuilder为建造者,不能实例化,StringBuild为指挥者和具体建造者,但是是由AbstractStringBuilder建造的。
适配器模式
将一个类的接口转化为用户可以使用的接口,如c++的queue和stack为deque的适配器
类适配器
一般为继承,一个类继承了另外一个类,通过简化接口,达到适配的效果
对象适配器
…