原则与设计模式

128 阅读10分钟

一. 六大原则

  1. 开放封闭(对拓展开放, 对修改关闭)
  2. 单一职责
  3. 里氏替换(任何基类可以出现的地方,子类一定可以出现)
  4. 依赖倒置(面向接口, 抽象不应依赖于细节,细节应依赖抽象。换言之,要针对接口编程,而不是针对实现编程)
  5. 接口隔离(使用多个小接口而不使用单一的总接口. 接口设计的尽量小, 不要暴露太多别人用不到的方法. 不要强迫客户依赖于那些他们不使用的方法。太小会导致系统中接口泛滥,不利于维护;接口太大将违背接口隔离原则,灵活性较差,使用起来很不方便)
  6. 迪米特法则(又叫:最少知识原则)(迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。不要暴露类的太多细节, 尽量全用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: 聚合(桥接模式)

聚合 image.png 实现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种角色

  1. Handle: 资源句柄, 可以是一个文件句柄, 一个socket句柄, 一个fd
  2. Synchronous Event Demultiplexer: 同步事件多路分离器, 用于阻塞等待发生在句柄集合上的一个或多个事件(如select/epoll)
  3. Event Handler:事件处理接口
  4. Concrete Event Handler:事件处理接口的具体实现 reactor模式

b) 业务流程及时序图

业务流程及时序图

  1. 程序启动, 调用Reactor的注册接口, 注册事件句柄
  2. 调用Reactor的消息循环, 进入无限循环, 等待完成事件到来
  3. 事件到来,select返回,Reactor将事件分发到之前注册的回调函数中处理

Proactor模式

a) 一共7种角色

  1. Handle: 资源句柄, 可以是一个文件句柄, 一个socket句柄, 一个fd
  2. Asynchronous Operation Processor:异步操作处理器;负责执行异步操作,一般由操作系统内核实现
  3. Asynchronous Operation:异步操作
  4. Completion Event Queue:完成事件队列, 异步操作完成的结果放到队列中等待后续使用
  5. Proactor:主动器, 为应用程序进程提供事件循环;从完成事件队列中取出异步操作的结果,分发调用相应的后续处理逻辑
  6. Completion Handler:定义事件回调接口
  7. Concrete Completion Handler:事件回调接口的具体实现

Proactor模式

b) 业务流程及时序图

业务流程及时序图

  1. 应用程序启动,调用异步操作处理器提供的异步操作接口函数,调用之后应用程序和异步操作处理就独立运行;应用程序可以调用新的异步操作,而其它操作可以并发进行
  2. 应用程序调用Proactor的消息循环, 进入无限循环,等待完成事件到来
  3. 异步操作处理器执行异步操作,完成后将结果放入到完成事件队列
  4. Proactor从完成事件队列中取出结果,分发到相应的完成事件回调函数处理逻辑中

Reactor与Proactor区别点

  1. Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据. 这里写的动作, 是需要慢慢等待写完成, 再进行后续的操作.
  2. Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑, 不需要做任何等待写完成的耗时.

可以看出Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作; Proactor直接调用异步读写操作,调用完后立刻返回;

  • reactor模式下, 如果处理耗时较长, 会造成事件分发的阻塞,影响到后续事件的处理**
  • proactor模式, 依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少**