设计模式开发文档 - 图1
请像对待每一行代码一样对待每篇文章 —- 鹰嘴豆

学习目标


  • 设计模式是一套经过时间检验、能让代码更好管理、重用的解决方法,每种设计模式都是解决代码开发中经常遇到的一类问题

  • 设计模式贯彻着接口,接口这里可以看成一种规范、一种约定,不同的类可以有不同的实现可以有个性化的功能,但是这种规范是要遵守,以后可以透明不同实现规范的类,用户只需要关注规范接口,知道接口中每个方法的作用就可以根据需求去调用,不用在意具体的实现细节

  • 设计模式长得都比较像,因为都是面相接口编程,不同的是接口如何抽象,抽象几个接口,抽象的接口的对象关系又如何

  • 接口中的方法抽象好以后后期不应该再往里面增加新的方法(开闭原则),但是jdk8提供了默认方法,如果增加也不是不可以。设计模式的设计是不违反开闭原则下的玩的一套变戏法,可能在实际开发中,设计模式应用会显得非常的累赘和麻烦,如果不能确定后期的业务变化而无法选择哪种稳定的设计模式(以后确定不会再变),那么还是不要随意滥用设计模式

  • 设计模式的好处。不会违反开闭原则,易于扩展

开始学习


设计模式的六大原则

  1. 开闭原则(Open Close Principle)

    1. 开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

    2. 使用面相接口编程,实现开闭原则前提规划好接口中的方法,一旦确定了就不能扩展新的接口方法,通过新增接口实现类来达到后期需求的迭代的要求

  2. 里氏代换原则(Liskov Substitution Principle)

    1. 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

    2. 程序有静态链接和动态链接,动态链接保证了多态的实现,lsp是多态的描述。在实现中调用基类的抽象方法或者接口方法,具体这个抽象方法或者接口方法如何实现就要看派生类是如何实现了,派生类实现中还可以复用基类一些方法

  3. 依赖倒转原则(Dependence Inversion Principle)

    1. 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

    2. 依赖于接口或者抽象可以达到同类行为的复用,后期只要扩展派生类或者接口实现类就可以完成其他功能的扩展

  4. 接口隔离原则(Interface Segregation Principle)

    1. 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

    2. 保证接口的单一原则,一个接口完成一种功能,不要将不同的功能耦合在一个接口上

  5. 迪米特法则,又称最少知道原则(Demeter Principle)

    1. 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

    2. 设计类时尽量减少使用合成复用原则,让类干净独立

    3. 如何让类在满足最少知道原则下能获取其他类的功能呢? 比如C类 需要用到A类和B类,那么正常情况下不可避免需要在C类中组合A类和B类的对象才能复用他们的功能,如果运用最少知道原则我们又如何做呢? 增加一个中间层,由中间层去管理A类和B类的对象,C类只要从中间层获取A类和B类的功能就行了,这样也能减少出错

  1. 合成复用原则(Composite Reuse Principle)

    1. 合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

    2. 在类中如果要依赖其他类的功能,我们尽量使用组合方式,而不是用继承,继承会让类层次结构变的复杂难维护,到最后很难扩展,对基类扩展很可能会影响其它子类的功能

创造型模式,
提供了创建对象的一些方法,比直接new更加的灵活,具体模式有:工厂模式、抽象工厂模式、单例模式、创造者模式、原型模式,下面具体探究下这几种模式的用法

  1. 工厂模式

    1. 工厂模式隐藏了具体实例化对象的一些细节,只要对外提供一个通用的接口就可以完成对象的创建及实例化

    2. 类库使用者其实并不要关心对象是如何创建的,而且其对象实例化往往比较复杂,可能存在多种途径去实例化,类库使用者只要通过配置文件的配置并调用类库提供的工厂方法就能完成对象的创建

    3. 工厂方法能够提供一个便捷的对象创建方法,保证客户端代码的干净及降低使用的难度

    4. 工厂模式也是基于接口编程的,通过其子类来决定使用哪种实例化方法

    5. 基于接口编程的共同弊端就是实现类的成倍增加,因此工厂模式也会存在这种问题,子类一多代码就变的难维护,如果后期需求不明确的情况下应该避免使用设计模式,在后期代码重构的时候选用特定的设计模式来解决特定的问题

    6. spring的依赖注入能够解决使用工厂方法时对具体实现类的依赖

    7. 提供一个具体的工厂类,在内部决定使用哪一种工厂方法实现类,类库使用者只要使用这个工厂类提供的方法就可以创建对象

    8. 用类图来描述工厂模式
      设计模式开发文档 - 图2

    9. 参考链接:菜鸟教程-工厂模式

  2. 抽象工厂模式

    1. 一个抽象的工厂类中提供多个抽象方法来获取不同的其他工厂对象

    2. 抽象工厂模式违反了类的单一原则,开闭原则,后期要支持新的工厂方法,需要在抽象工厂类中扩展代码

    3. 抽象工厂模式的好处是将所有的工厂类聚集起来,这样便于管理和用户使用

    4. 抽象工厂类需要有其他工厂方法类来继承,需要实现一些没必要的方法所以有点违反继承的使用规则,个人觉得这里将继承改成组合,在抽象工厂类中指定其他不同工厂类的组合,并提供不同工厂方法的实例化

    5. 菜鸟教程提供的抽象工厂类图,可以看到ShapeFactory继承AbstractFactory时多实现了一个getColor这个没有意义的方法,也就是说AbstractFactory和ShapeFactory不完全存在has-a的关系
      设计模式开发文档 - 图3

    6. 改造这个抽象方法,个人觉得使用下面的类图来完成一个工厂类提供其他工厂类的初始化
      设计模式开发文档 - 图4

  3. 单例模式

    1. 单例模式有两种模式,一种是饿汉式但是它线程不安全,一种是另外一种是提供一个对外的接口进行初始化,它线程安全

    2. 对外提供接口的单例模式使用双重检查来保证线程安全

  4. 创建者模式

    1. 限制太多,要求同一类型的集合,并需要一个创建者类提供不同的创建方法决定集合中的元素种类和数量,构建方法在后期必然会违反开闭原则,还需要一个类提供这些集合元素的稳定的一套计算逻辑

    2. 需要明白一点,做到代码的通用就必定导致代码不够灵活,有零零总总的限制,特别使用集合来做循环处理必须要求元素同一种类型,这些元素就需要实现一个特定的接口

    3. 集合和设计模式配合注定会导致一个弊端,就是需要集合中的元素都实现同一个接口,这样大大的限制了创建者模式的使用,在实际开发中不会这么理想让接口中的方法都被后面扩展的元素需要,也就是说这个接口的设计难度非常的大,后期等代码稳定以后可以重构看看能不能使用创建者模式来处理

    4. 创建者模式的类图,Meal是一个提供稳定算法的类,只要通过MealBuilder提供items元素就可以,也就是说会变的只不过是items元素
      设计模式开发文档 - 图5

    5. 为上面的MealBuilder提供MealBuilder接口,VegMealBuilder、NonVegMealBuilder是它的实现类,在接口实现上面提供的prepareVegMeal和prepareNonVegMeal实现逻辑,这样就可以避免后期创建方法扩展时违反开闭原则,只不过保证开闭原则必定会产生类成倍增长的弊端,具体看怎么取舍

    6. 代码通用性高注定代码不够灵活,上面的设计模式使用集合和接口做到通用,但也产生了条条框框的限制,下面的类图放开元素必须实现同一个接口的限制,但是却无法做到代码通用,会产生冗余的代码,这是用面相对象编程就无法满足了,可以尝试使用面相切面编程
      设计模式开发文档 - 图6

  1. 原型模式

    1. 原型模式就是利用缓存来解决重复创建对象时的性能消耗问题

    2. 提供一个Map作为缓存,因为是集合所以它的元素需要实现同一种接口

    3. 体用Map缓存类提供加缓存的方法,也提供获取缓存的方法

    4. 原型模式的类图如下所示
      设计模式开发文档 - 图7

  1. 以上的设计模式来自菜鸟教程,发现和其它的设计模式有些出入,且菜鸟继承提供的些类图设计不合理,特在此备注

结构性模式,存在下面的几种设计模式

  • 适配器模式(Adapter Pattern)

  • 桥接模式(Bridge Pattern)

  • 过滤器模式(Filter、Criteria Pattern)

  • 组合模式(Composite Pattern)

  • 装饰器模式(Decorator Pattern)

  • 外观模式(Facade Pattern)

  • 享元模式(Flyweight Pattern)

  • 代理模式(Proxy Pattern

  1. 适配器模式

    1. 适配器模式是针对老代码、第三方类无法再做重构但是在本地代码中需要融合多种方式,并提供统一的接口来调用,这时候可以增加一个适配器类做专门的处理,使用者就不需要关心具体使用哪几种第三方库实现,这很符合最少知道原则,减少使用中的错误

    2. 最少知道原则一般都是增加一个中间层来完成

    3. 适配器在适配第三方代码或者老的代码的时候可以增加一些额外的处理,只要对外提供方法即可,

    4. 适配器类图如下
      设计模式开发文档 - 图8

    5. 使用适配器可以将丑陋的代码在新的类中包装,满足后期的扩展

  2. 桥接模式

    1. 将抽象部分和实现部分分离,使他们可以独立的变化

    2. 桥接模式目的就是将庞大的类中的功能更细致的划分,将后期多样性的功能移到其他类中可通过接口、抽象类的形式实现多样化,这个庞大的类移除一部分实现以后还可以让类通过继承自己的形式来扩展新的功能或者覆写旧的功能

    3. 桥接模式更多是指导如何将一个庞大的类进行重构优化成已维护的类,组合、接口、继承三种方式来实现

    4. 桥接模式的类图如下
      设计模式开发文档 - 图9

  1. 组合模式

    1. 定义:将对象组合成树形结构以表示部分-整体的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性

    2. 以树的形式将部分组装成一个整体,需要用到集合,集合也说明了部分和整体需要相同的类型,也就是要么用接口要么用继承来表示部分这个类

    3. 类似于vue的组件,将一个大的类文件按照功能分割成多个小功能组件。这些组件在其他地方都可能被复用,这些组件统一指定一种父类型(实现统一接口或者继承同一个抽象类)。组合模式组装这些组件是以树结构的形式,也就是说每个组件都有一个child的集合属性来存放子组件,各自的组件都有组织展现child集合的逻辑实现

    4. 实战应用:暂无

    5. 类图如下
      设计模式开发文档 - 图10

  1. 装饰模式

    1. 动态的给一个对象增加一些额外的职责,就增加功能来说,装饰模式比生成子类更加的生动

    2. 装饰模式也是一种调整类代码的一种手段,可以将代码写在一起,也可以将代码按照装饰模式分离成核心功能和装饰功能。这里核心功能是不会变的,装饰功能是会变的,会变的功能一般都是通过继承子类或者实现接口来完成

    3. 子类继承基类,可以在子类重写的方法中调用其基类的方法,这种方式可以在子类方法中增加一些装饰的代码,而核心的代码处理还是交给基类的方法。如果想要达到调用链的效果,可以在基类中引入一个关联子类的对象,如同的树结构设计一样,调用的效果类似于树的递归,可以实现一个核心代码被N层装饰包装修饰

    4. 类图设计
      设计模式开发文档 - 图11

  2. 外观模式

    1. 定义:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一个子系统更加容易使用

    2. 外观模式遵守最少知道原则降低客户端对子系统类的使用,通过增加一个接口来对子系统类功能的代理去完成客户端的相关请求,客户端只需要知道这个接口即可,不需要去知道子系统中其他复杂的类簇

    3. 思想和适配模式很像,都是对旧的难理解的类进行包装来降低使用难度,所以从思想角度理解会更好

    4. 类图如下
      设计模式开发文档 - 图12

  3. 享元模式

    1. 定义:应用共享技术有效的支持大量细粒度的对象

    2. 共享的实现就是增加缓存,对象存在则返回对象,不存在则创建在返回该对象。这里的对象就是细粒度化后的享元类对象

    3. 享元模式有点像创造型分类的原型模式,对创建对象的复用。享元模式可以从代码划分的角度去理解,将一个大的类划分成不同粒度的类,通过接口来统一管理这些粒度的类,享元模式还要求共享这些粒度的类,共享的方式就是缓存

    4. 类图如下
      设计模式开发文档 - 图13

  1. 代理模式

    1. 定义:为其他对象提供一种代理以控制对这个对象的访问

    2. 熟悉的Spring的AOP将一个普通对象织入切面后生成的代理对象

    3. 加入接口或者基类是为了达到统一调用的效果

    4. 类图如下
      设计模式开发文档 - 图14

行为型模式-关注对象间的通信

  • 责任链模式(Chain of Responsibility Pattern)

  • 命令模式(Command Pattern)

  • 解释器模式(Interpreter Pattern)

  • 迭代器模式(Iterator Pattern)

  • 中介者模式(Mediator Pattern)

  • 备忘录模式(Memento Pattern)

  • 观察者模式(Observer Pattern)

  • 状态模式(State Pattern)

  • 空对象模式(Null Object Pattern)

  • 策略模式(Strategy Pattern)

  • 模板模式(Template Pattern)

  • 访问者模式(Visitor Pattern)

  1. 责任链模式

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

    2. 责任链模式机制和装饰模式很像, 实现方式也是链式形成递归调用,从一层执行到下一层,一层层慢慢深入,区别在于装饰模式是每一层都需要处理,而责任链是一层层下去执行符合条件的那一层逻辑然后结束

    3. 实现原理:统一定义一个接口是必须的,这样才能在达到统一调用的效果,要想实现一层到下一层的效果,组合一个下一层对象(一个接口的实现类对象)是必须的,在处理方法中如果符合条件则处理,不符合就调用这个下层对象执行接口实现的规范完成下一层代码逻辑

    4. 类图如下
      设计模式开发文档 - 图15

  1. 命令模式

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

    2. 命令模式将对象间的通信方式很好的诠释,通过接口关联不同的实现类执行不同的操作,从下面类图可以看到命令模式关联了两个接口,一个是Command还有一个就是Receiver,所以两者都有不同的选择,实现解耦和容易扩展

    3. 类图如下
      设计模式开发文档 - 图16

  2. 解释器模式

  3. 迭代器模式

  4. 中介者模式

  5. 备忘录模式

  6. 观察者模式

  7. 状态模式

  8. 空对象模式

  9. 策略模式

  10. 模板模式

  11. 访问者模式

  1. 访问者模式
    I、定义:表示一个作用于某对象结构中的各个元素的操作,它使你可以在不改变各元素的类的前提的定义作用于这些元素的新操作。
    II、好处:设计模式通用好处:不违反开闭原则,只要实现接口就能不断扩展后期不断迭代的代码。访问者模式好处:想要改变数据结构(如集合)中的元素,不需要更改这些元素的类,只需要将这些元素交给专门修改的新操作类即可
    III、弊端:接口中抽象的方法要明确在后期的扩展中不会再新增,也就是说接口中的动作固定那么几种
    IV、约束:各种元素的种类需要被明确,因为在访问者模式中根据元素的种类抽象出一个接口规范,让新操作类去实现这个接口规范
    V、实现:各种元素的类中提供一个接收访问者的方法,将该元素的this对象传递给访问者专门为该元素类准备的方法中,在这个方法中调用传递过来元素对象执行额外的操作
    VI、访问者类图,下面有四种元素Computer、keyboard、mouse、mointor,这四种元素都有额外功能要执行,这些功能抽象出来放到访问者computePartVisitor接口中,里面定义了四种元素的访问执行方法,通过不同版本去实现这个接口来完成额外功能
    设计模式开发文档 - 图17
    VII、总结:细细体会了这个设计模式发现 ——> 集合中的每一个元素都有额外的乱七八糟的功能要执行,而这些操作又不能放到具体的元素类里面去定义方法来完成(因为这些乱七八糟的操作有很多不同版本,不可能在元素类中不断的增加对应的方法—开闭原则,因此解决这种不同版本的功能最先想到就是用接口来做),在这些元素类中提供一个接受访问者对象的方法,这个访问者是一个接口,具体实现类可以在外部指定,这样就能应用指定的额外功能版本。不同版本可以通过实现访问者接口扩展
    VIII、其他学习资料:链接:菜鸟教程-访问者模式

学习总结


  1. 设计模式的一些变戏法手段

    1. 通过实现接口或者继承基类来面向接口编程,动态连接来决定具体执行时是那个对象,面向接口编程的好处在于使用规范提供的方法,会用即可,不必在意实现,让实现接口的开发者遵守接口规范去实现具体的实现类来提供功能

    2. 使用关联其他类来引入新功能,可以用集合接收一组实现了同一个接口的对象,也可以单独只关联一个对象

    3. 接口实现类中对关联的集合对象做循环处理

    4. 子类继承基类,可以在子类重写的方法中调用其基类的方法,这种方式可以在子类方法中增加一些装饰的代码,而核心的代码处理还是交给基类的方法。如果想要达到调用链的效果,可以在基类中引入一个关联子类的对象,如同的树结构设计一样,调用的效果类似于树的递归,可以实现一个核心代码被N层装饰包装修饰

    5. 加入接口或者抽象类是为了达到统一调用的效果,实现在共用代码中通过同一种接口规范来实现调用实现类的功能,设计模式都是基于这个特点来完成不同模式的实现

贡献者列表


编辑人 编辑时间 编辑内容
鹰嘴豆 2018/12/15 初稿