参考:

一.定义

  • 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

    二.作用

  • 使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

    1.提高代码复用率,降低开发成本和周期 2.提高代码可维护性、可拓展性 3.使代码更加优雅 4.让代码更容易被他人理解

三.设计原则

  • 对接口编程而不是对实现编程。
  • 优先使用对象组合而不是继承。

    1.单一职责

  • 定义:一个类应该只有一个发生变化的原因

    • 白话:如果一个类承担的职责过多,即耦合性太高=一个职责的变化可能会影响到其他的职责
  • 特点

    1. 降低类的复杂性, 对类或接口的职责有清晰明确定义;
    2. 提高可读性;
    3. 提高可维护性;
    4. 降低变更引起的风险, 接口改变只影响相应的实现类, 不影响其他类;
  • 重点
    1. 接口一定要做到单一职责;
    2. 类的单一职责比较难以实现, 尽量做到只有一个原因引起变化;
    3. 一个方法尽可能做一件事, 能分解就分解, 分解到原子级别;
  • 补充:

    • 对职责定义,如何划分,每个人的理解是不一样的。需要根据经验和实际的业务逻辑而定。当然,也有些最基本的原则,比如:两个不想关的函数就不应该放在一个类里面,一个类应该是相关性很高的数据、函数的封装。根据具体的业务、功能对类进行相应的划分,是程序员迈出优化的第一步;

      2.开闭

  • 定义:软件中的对象(类, 模块, 函数)应该对扩展开放, 对修改关闭;

    • 即软件实体应该通过扩展实现变化, 不是通过修改已有的代码实现变化;
    • 符合开放封闭原则的最好方式是提供一个固有的接口,然后让所有可能发生变化的类实现该接口,让固定的接口与相关对象进行交互。
  • 优点

    • 利于测试:如果改变软件内容, 需要将所有的测试流程都执行一遍, 如 单元测试, 功能测试, 集成测试等, 如果只是扩展, 只单独测试扩展部分即可;
    • 提高可维护性 : 维护一个类最好的方式是 扩展一个类, 而不是修改一个类, 如果需要修改需要读懂源码才能修改, 扩展的话只需要了解即可, 直接继承扩展;
  • 补充:

    • 开闭原则(OCP)是Java世界里面最基础的设计原则,指导着我们建立灵活、稳定的系统。在软件的生命周期中,会因为变化、升级、维护等原因需要对原有代码进行修改,修改就可能引入错误到原来原本经过测试的旧代码,会破坏原本的系统。为了避免这种问题的发生,我们应该遵循对修改关闭,对拓展进行开放。如何确保原有模块的正确性,以及尽量少的影响原有模块,答案就是遵循开闭原则;

      3.里氏替换

  • 定义:所有 引用基类的地方 必须能 透明地使用其子类的对象;

    • 白话:只要 父类出现的地方子类就可以出现, 替换为子类也不会产生任何错误, 使用者不需要知道父类还是子类;
    • 需求(场景):玩家玩射击气球游戏,他既能使用AK仿真枪,也可以使用步枪还可以使用手枪射击气球。
  • 在软件开发过程中,子类替换父类后,程序的行为是一样的。

  • 只有当子类替换掉父类后软件的功能不受影响时,父类才能真正地被复用,而子类也可以在父类的基础上添加新的行为。
  • 特点:
    1. 里氏替换原则核心是继承,同样它的优缺点也是继承的优缺点;
  • 继承的优缺点(补充)
    1. 优点
      1. 代码共享 : 共享代码, 子类都拥有父类的方法和属性, 将 父类的代码共享给了子类;
      2. 重用性 : 提高代码的重用性, 子类重用父类的代码;
      3. 子父类异同 : 子类形似父类, 异于父类, 父子都不同;
      4. 扩展性 : 提高代码的可扩展性, 子类就可以为所欲为了, 子类可以随意扩展父类;
      5. 开放性 : 提高产品或项目的开放性, 父类随意扩展, 开放性随之增加了;
    2. 缺点
      1. 侵入性 : 继承是侵入性的, 子类 强制继承 父类的方法和属性;
      2. 灵活性 : 降低代码的灵活性, 子类必须拥有父类的属性和方法, 子类受到了父类的约束, 这是从子类的角度讲得;
      3. 耦合性 : 增强了耦合性, 父类的属性和方法被修改时, 还需要顾及其子类, 可能会带来大量的重构, 这是从父类的角度讲的;
  • 补充

    • 里氏替换是指导我们构建出拓展性更好的软件系统;
    • 里氏替换和开闭原则往往是生死相依、不离不弃的,通过里氏替换达到对拓展开放,对修改关闭;这两个原则都强调了OOP的一个重要特性—抽象(是走向优化的重要一步)。

      4.依赖倒置

  • 定义:依赖于抽象而不依赖于具体。

  • 针对接口编程,开闭原则的基础;
  • 高层模块不应该依赖低层模块,抽象不应该依赖细节,高层模块和细节应该都依赖其抽象。

    • 所谓的 “面向接口编程,而不是面向实现编程”。这样可以降低客户与具体实现的耦合。
    • 补充:**
  • 优点

    1. 减少类之间的耦合
    2. 提高系统稳定性
    3. 降低并发风险
    4. 提高代码可读性
  • 依赖注入的实现(略)
    1. 构造函数:通过 构造函数参数 声明依赖对象;
    2. Setter方法:通过 Setter 函数 参数 声明依赖对象;
    3. 接口注入依赖对象:在接口方法的参数中声明依赖对象;
  • 依赖倒置本质
    • 通过 抽象 即 接口或者抽象类, 使 各个类 和 模块实现彼此独立, 实现模块间 松耦合;
  • 使用场景
    • 依赖倒置在小项目中得有点很难体现出来, 是否采用依赖倒置原则影响不大;
    • 项目越大, 需求改变越多, 采用依赖倒置原则设计的接口或抽象类 对 实现类的约束, 会大大减少维护成本;
  • 补充

    • 依赖倒置原则在Java语言中的具体表现:模块之间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
    • 一句话概括:面向接口编程(是面向对象的精髓之一),或者说是面向抽象编程;
    • 如果类与类之间依赖于细节,那么它们之间就有直接的耦合,当具体实现需要变化时,意味着要同时修改依赖者的代码,这限制了系统的可拓展性。

      5.接口隔离

  • 使用多个专门功能的接口,而不是使用单一的总接口。

    • 不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。
  • 单一职责 与 接口隔离 区别 :

    1. 单一原则 注重职责, 注重业务逻辑上得划分;
    2. 接口隔离 注重的是接口的方法尽量少;
  • 特点
    • 接口尽量小:拆分接口 ,接口隔离的核心定义, 不出现臃肿的接口;
    • 接口高内聚:高内聚 ,提高接口, 类, 模块的处理能力, 减少对外界交互;
      • 具体方法 : 接口中尽量少公布public 方法, 对外公布的 public 方法越少, 变更的风险就越小, 有利于后期的维护;
  • 原子接口划分原则
    • 接口模块对应关系 : 一个接口只服务于一个子模块 或 业务逻辑;
    • 方法压缩 : 通过业务逻辑, 压缩接口中的 public 方法, 减少接口的方法的数量;
    • 修改适配 : 尽量去修改已经污染的接口, 如果变更风险较大, 采用适配器模式进行转化处理;
  • 补充

    • 类间的依赖关系应该建立在最小的接口上。接口隔离的原则是将更大,更臃肿的接口拆分成更小和更具体的接口,客户只需要知道其感兴趣的接口。其目的是为了让系统解耦合,从而更容易重构和重新部署。

      6.合成复用

  • 在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。

    • 新对象通过向这些对象的委派达到复用已用功能的目的。
    • 简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。

7.迪米特(最少知道)

  • 一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。

    • 关于迪米特法则的其他描述:只与你直接的朋友们通信;不要跟“陌生人”说话。
    • 外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。
  • 设计方法

    • 一个类的 public 方法越多, 修改时涉及的范围也就越大, 变更引起的风险也就越大, 在系统设计时要注意, 能用 private 就用private , 能用 protected 就用 protected, 能少用 public 就少用 public, 能加上 final 就加上 final;
  • 优缺点

    • 优点:类间解耦, 弱耦合, 耦合降低, 复用率提高;
    • 缺点:类间的耦合性太低, 会产生大量的中转或跳转类, 会导致系统的复杂性提高, 加大维护难度;

      8.总结

  • 在应用开发过程中,最难的不是完成应用的开发工作。而是在后续的升级、维护过程中让应用系统能够拥抱变化。拥抱变化就意味着在满足需求且不破坏系统稳定性的前提下保持高拓展性、高内聚、低耦合,在经历了个版本的变更之后依然保持清晰、灵活、稳定的系统架构。当然,这是比较理想的状态,我们需要朝着这个目标去做,最好的方式就是遵循以上的设计原则。

    四.分类

    1.创建型

  • 不是使用 new 运算符直接实例化对象,而是提供一种方式-在创建对象的同时隐藏创建逻辑;

  • 好处:这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

    1.1.工厂模式(Factory Pattern)

    1.2.抽象工厂模式(Abstract Factory Pattern)

    1.3.单例模式(Singleton Pattern)

    1.4.建造者模式(Builder Pattern)

    1.5.原型模式(Prototype Pattern)

    2.结构型

  • 关注类和对象的组合

    2.1.适配器模式(Adapter Pattern)

    2.2.桥接模式(Bridge Pattern)

    2.3.过滤器模式(Filter、Criteria Pattern)

    2.4.组合模式(Composite Pattern)

    2.5.装饰器模式(Decorator Pattern)

    2.6.外观模式(Facade Pattern)

    2.7.享元模式(Flyweight Pattern)

    2.8.代理模式(Proxy Pattern)

    3.行为型

  • 关注对象之间的通信

    3.1.责任链模式(Chain of Responsibility Pattern)

  • 定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系,

将这些对象形成一条链,并沿着这条链传递该请求,直到有对象处理它为止;

  • 重点:上一个处理对象必须含有下一个处理对象的引用,形成一个单向链表;

    3.2.命令模式(Command Pattern)

    3.3.解释器模式(Interpreter Pattern)

    3.4.迭代器模式(Iterator Pattern)

    3.5.中介者模式(Mediator Pattern)

    3.6.备忘录模式(Memento Pattern)

    3.7.观察者模式(Observer Pattern)

    3.8.状态模式(State Pattern)

    3.9.空对象模式(Null Object Pattern)

    3.10.策略模式(Strategy Pattern)

    3.11.模板模式(Template Pattern)

    3.12.访问者模式(Visitor Pattern)