一. 六大原则
- 开放封闭(对拓展开放, 对修改关闭)
- 单一职责
- 里氏替换(任何基类可以出现的地方,子类一定可以出现)
- 依赖倒置(面向接口, 抽象不应依赖于细节,细节应依赖抽象。换言之,要针对接口编程,而不是针对实现编程)
- 接口隔离(使用多个小接口而不使用单一的总接口. 接口设计的尽量小, 不要暴露太多别人用不到的方法. 不要强迫客户依赖于那些他们不使用的方法。太小会导致系统中接口泛滥,不利于维护;接口太大将违背接口隔离原则,灵活性较差,使用起来很不方便)
- 迪米特法则(又叫:最少知识原则)(迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。不要暴露类的太多细节, 尽量全用private)
二. 23种设计模式
| 分类 | 设计模式 |
|---|---|
| 创建型(共5种) 提供一套在创建对象的同时隐藏创建逻辑的方法. | 1. 工厂方法(Factory Method) 2. 抽象工厂(Abstract Factory) 3. 单例(Singleton) 4. 建造者(Builder) 5. 原型(Prototype) |
| 结构型(共7种) 提供类的各种继承,组合,聚合的玩法 | 1. 适配器模式(Adapter) 2. 桥接模式(Bridge) 3. 组合模式(Composite) 4. 装饰器模式(Decorator) 5. 外观模式(Facade) 6. 享元模式(Flyweight) 7. 代理模式(Proxy) |
| 形为型(共11个) 对象之间的通信 | 1. 责任链(Chain of Responsibility) 2. 命令(Command) 3. 解释器(Interpreter) 4. 迭代器(Iterator) 5. 中介者(Mediator) 6. 备忘录(Memento) 7. 观察者(Observer) 8. 状态(State) 9. 策略(Strategy) 10. 模板(Template) 11. 访问者(Visitor) |
2.1. 简单工厂, 工厂方法, 抽象工厂的区别
| 模式 | 描述 | 优缺点 |
|---|---|---|
| 简单工厂 | 一个工厂通过switch...case生产产品 | 每增加一个新产品, 都得改动工厂的代码, 违反开闭原则 |
| 工厂方法 | 有多少产品就开多少工厂 | 符合开闭原则不用改老代码, 但增加新产品就得增加对应的工厂 |
| 抽象工厂 | 在工厂方法基础上扩展成可生产多种产品 |
- 2.1.1. 简单工厂
- 2.1.2. 工厂方法
- 2.1.3. 抽象工厂
2.2. 建造者模式(又叫创建者模式)
当一个类的内部数据过于复杂的时候, 要创建的话可能就需要了解这个类的内部结构,还有这些东西是怎么组织装配等一大坨乱七八糟的东西,这个时候就会增加学习成本而且会很混乱,这个时候就想啊想一种什么法子来管理一下这个类中的数据呢,怎么在创建的时候让它按部就班的来,并且代码可读性很好别让我看花了眼啊,我要的东西也能都很好设置进来,这就是Builder模式的应用场景
比如我就想买一台粉红色的笔记本, 我不想知道它是怎么用主板显示器等组装的
Computer pc = ComputerBuilder.setColor("pink").build();
2.3. 原型模式
用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式, 适用于创建重复的对象, 类似java中的clone和C++中的拷贝构造.
2.4. 桥接模式
用聚合取代继承 示例: 实现画各种颜色的图形
方案A: 继承
需要先写4个父类, 实现4种图形. 再写12个子类, 实现画3种颜色. 类太多, 代码冗余也多.
方案B: 聚合(桥接模式)
实现4个图形类和3个颜色类, 两者通过聚合关联在一起即可
Shape shape = new Rectangle;
Color color = new Color("red");
shape.setColor(color);
shape.draw(); // 画出一个红色的矩形
2.5. 外观模式, 组合模式的区别
外观模式: 统一接口. 它是一个容器, 将很多小的对象, 组合成一个大的对象, 对外提供统一的操作接口,来操作这些小对象.
组合模式: 比如窗口控件之间的多层次组合关系, 窗口套控件, 控件套窗口, 将对象组合成树形结构以表示"部分-整体"的层次结构, 所以又叫部分整体模式
2.6. 静态代理和动态代理
隔离, 非直接操作. 一个类代理另一个类的一部分功能,代理类只是代理,并非直接操作 静态代理, 需要我们为每个类写一个代理, 如果类多了, 写起来很费劲. 动态代理, 会在运行期, 帮我们生成一个代理.
2.7 装饰器模式
使用聚合代替继承, 装饰类和被装饰类可以独立发展,不会相互耦合 一般的,我们为了扩展一个类经常使用继承方式实现,随着扩展功能的增多,子类会很膨胀。适用于不想增加更多子类的情况下扩展类.
装饰类持有对被装饰者的引用,且与被装饰者实现同一个接口, 如下图:
2.8. 访问者, 观察者 的区别
观察者, 就是回调. 被观察者发生变更时会通知观察者, 可以有多个观察者. 访问者, 将稳定的数据结构和易变的操作耦合问题, 类似于Qt的Model/View模型, 同一组数据, 不同的访问者关注点不一样. 对数据进行的操作也不一样.
2.9. 中介者模式
降低多个对象和类之间的通信复杂性.
适用于解决多个类相互耦合,形成了网状结构。 对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
主要是将网状结构分离为星型结构, 如下图
2.10. 模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.
如: 父窗口提供接口和对接口的一些调用,子窗口重写一些接口,父窗口会按生命周期顺序调用这些接口
示例:
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
// 模板方法
public final void play() {
initialize();
startPlay();
endPlay();
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play(); // 调用模板方法
game = new Football();
game.play(); // 调用模板方法
}
}
2.11. 策略模式, 状态模式的区别
| 模式 | 关键字 | 描述 |
|---|---|---|
| 策略模式 | 封装一系列的算法策略, 可以相互替换 | 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略 |
| 状态模式 | 类的行为是基于它的状态改变的 | 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。 |
- 策略模式
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换.
解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护.
- 状态模式
当代码中包含大量与对象状态有关的条件语句时, 可以使用状态模式
其它几个没提到的模式
| 模式 | 关键字 | 描述 |
|---|---|---|
| 单例 | 全局变量 | 构造函数私有, 对外提供一个静态函数getInstance |
| -- | -- | -- |
| 适配器 | 兼容 | 把vector适配成stack接口 |
| 享元 | 池的概念 | 主要用于减少创建对象的数量,以减少内存占用和提高性能 |
| -- | -- | -- |
| 责任链 | 一条链 | 窗口父子控件的消息传递就是责任链模式 |
| 命令 | 往线程池里扔任务 | 将命令本身,发送者,执行者,三者解偶 |
| 迭代器 | 解偶数据结构与算法 | 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示 |
| 解释器 | 终止表示式,非终止表示式 | 主要用于词法语法解析 |
| 备忘录 | 将类的状态,存储到结构体中, 可用该结构体恢复这个类当时的状态 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态 |
附加:
好莱坞原则
不要给我们打电话,我们会给你打电话
Reactor模式
a) 一共4种角色
- Handle: 资源句柄, 可以是一个文件句柄, 一个socket句柄, 一个fd
- Synchronous Event Demultiplexer: 同步事件多路分离器, 用于阻塞等待发生在句柄集合上的一个或多个事件(如select/epoll)
- Event Handler:事件处理接口
- Concrete Event Handler:事件处理接口的具体实现
b) 业务流程及时序图
- 程序启动, 调用Reactor的注册接口, 注册事件句柄
- 调用Reactor的消息循环, 进入无限循环, 等待完成事件到来
- 事件到来,select返回,Reactor将事件分发到之前注册的回调函数中处理
Proactor模式
a) 一共7种角色
- Handle: 资源句柄, 可以是一个文件句柄, 一个socket句柄, 一个fd
- Asynchronous Operation Processor:异步操作处理器;负责执行异步操作,一般由操作系统内核实现
- Asynchronous Operation:异步操作
- Completion Event Queue:完成事件队列, 异步操作完成的结果放到队列中等待后续使用
- Proactor:主动器, 为应用程序进程提供事件循环;从完成事件队列中取出异步操作的结果,分发调用相应的后续处理逻辑
- Completion Handler:定义事件回调接口
- Concrete Completion Handler:事件回调接口的具体实现
b) 业务流程及时序图
- 应用程序启动,调用异步操作处理器提供的异步操作接口函数,调用之后应用程序和异步操作处理就独立运行;应用程序可以调用新的异步操作,而其它操作可以并发进行
- 应用程序调用Proactor的消息循环, 进入无限循环,等待完成事件到来
- 异步操作处理器执行异步操作,完成后将结果放入到完成事件队列
- Proactor从完成事件队列中取出结果,分发到相应的完成事件回调函数处理逻辑中
Reactor与Proactor区别点
- Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据. 这里写的动作, 是需要慢慢等待写完成, 再进行后续的操作.
- Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑, 不需要做任何等待写完成的耗时.
可以看出Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作; Proactor直接调用异步读写操作,调用完后立刻返回;
- reactor模式下, 如果处理耗时较长, 会造成事件分发的阻塞,影响到后续事件的处理**
- proactor模式, 依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少**