在开始之前,先简单做一个设计模式的大概框架,总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

个人认为比较重要的模式有抽象工厂模式、单例模式、装饰器模式、策略模式。

1、适配器模式

描述:把一个类的接口转换成客户端所期待的另一种接口,从而使原接口不匹配而无法在一起工作的两个类能在一起工作。

举例

我十分喜欢索尼的ps系列游戏,但是众所周知,如果买国行的主机,那么就要面临着锁区的问题,所以就需要去购买港版的主机,然而,中国香港和中国澳门使用三角扁型插头,但大陆使用的是两脚扁型或者八字形插头,因此需要在主机插头外接一个转接器,而这个转换器插头就是起到了一个适配器的作用。

要素

目标(CTarget):定义一个客户端使用的特定接口。客户(CClient):使用目标接口,与和目标接口一致的对象合作。被适配者(CAdaptee):一个现存需要匹配的接口。适配器(CAdapter):负责将CAdaptee的接口转换成CTarget的接口。

适配器是一个具体的类,这是本模式的核心。由此可见,当客户端调用Adapter接口时候,Adapter便会调用Adaptee的操作相应请求,该模式就完成了接口的适配过程。

优缺点

优点:

1、可以让任何两个没有关联的类一起运行;

2、提高了类的复用;

3、增加了类的透明度;

4、灵活性好。

缺点:

1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构;

2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

使用场景

在两个类都不太好修改时,可以考虑使用适配器模式,否则应考虑重构。

分类

适配器模式主要分为类适配器模式对象适配器模式

“类适配器是通过继承类适配者类(Adaptee Class)实现的,另外类适配器实现客户类所需要的接口。当客户对象调用适配器类方法的时候,适配器内部调用它所继承的适配者的方法。对象适配器包含一个适配器者的引用(reference),与类适配器相同,对象适配器也实现了客户类需要的接口。当客户对象调用对象适配器的方法的时候,对象适配器调它所包含的适配器者实例的适当方法。”

2、外观模式

描述:外观模式定义了一个将子系统的一组接口集成在一起的高层接口,以提供一个一致的界面。通过这个界面,其他系统可以方便地调用子系统中的功能,而忽略子系统内部发生的变化。

举例

最直接的例子就是手机应用,譬如支付宝,我们只要简单的扫码,确认支付,就可以完成对商品的购买,但是在支付的背后,许多的子逻辑都被支付宝隐藏了,我们只能看到其对用户的接口,这就是最直接的外观模式。

优缺点

优点

1、降低系统间的耦合;

2、简化调用。

缺点

1、子系统间的扩展会有风险。

使用场景

1、为一个比较复杂的子系统提供一个简单的接口;

2、将客户程序与子系统的实现部分分离,提高子系统的独立性和可移植性;

3、简化子系统间的依赖关系。

3、组合模式

描述:将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。(来自GOF定义)

举例

譬如我要买对马岛之魂,ps5的游戏有以下几个选项:购买本体;购买线上模式;购买导剪版。所谓的导剪版就是一种组合模式,它既包括了游戏本体,又包括了线上模式,但是用户只要一次购买即可。

优缺点

优点

1、清楚地定义分层次的复杂对象,表示对象的全部或部分层次 ;

2、简化客户端代码。

缺点

1、使设计更加抽象。

使用场景

当需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同时,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。

4、桥接模式

描述:桥接器模式(BridgePattern)又称为桥梁模式,它主要用意是为了实现抽象部分与实现部分脱耦,使它们各自可以独立地变化。

举例

如果要用蜡笔画出红黄蓝三种颜色且有大中小型号的图画,一共需要3*3=9种型号;然而使用毛笔,只要3个大中小型号的毛笔与3种不同颜色的颜料就可以了,这就是抽象与实现的分离。

优缺点

优点

1、解耦;

2、可扩展。

缺点

1、使设计更加抽象。

使用场景

在以下的情况下应当使用桥梁模式:

1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系;

2、设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的;

3、一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。桥梁模式是一个非常有用的模式,也非常复杂,它很好的符合了开放-封闭原则和优先使用对象,而不是继承这两个面向对象原则。

5、责任型模式

描述:在责任链模式里,很多的对象由每一个对象对其下家的引用而联接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

举例

排查Jira的过程就是一个责任型模式,看到这个问题时,如果自己能处理,那么问题变终止了;如果不能处理,那么就会将其转给另一个责任人,直到问题处理完成,但是最后一步到底交给谁,这是不确定的,可以一定程度上的变换处理顺序。

角色

1、抽象处理者角色、定义出一个处理请求的接口;假如需要,接口可以定义出一个方法,以返回对下家的引用。

2、具体处理者(ConcreteHandler)角色、处理接到请求后,可以选择将请求处理掉,或者将请求传给下家。

优缺点

优点

1、解耦发布者与执行者;

2、更灵活,可扩展。

缺点

1、不方便调试;

2、请求可能不被接收。

使用场景

第一、系统已经有一个由处理者对象组成的链。这个链可能由复合模式给出;

第二、当有多于一个的处理者对象会处理一个请求,而且在事先并不知道到底由哪一个处理者对象处理一个请求。这个处理者对象是动态确定的;

第三、当系统想发出一个请求给多个处理者对象中的某一个,但是不明显指定是哪一个处理者对象会处理此请求;

第四、当处理一个请求的处理者对象集合需要动态地指定时。

6、单例模式

单例模式的几种实现:

饿汉式

线程安全

  1. public class EHan {
  2. // 直接实例化
  3. private static EHan instance = new EHan();
  4. // 私有方法
  5. private EHan() {
  6. }
  7. public static EHan getInstance() {
  8. return instance;
  9. }
  10. }

懒汉式

线程不安全

  1. public class LanHan {
  2. // 用到时再初始化
  3. private static LanHan instance;
  4. private LanHan() {
  5. }
  6. public static synchronized LanHan getInstance() {
  7. if (instance == null) {
  8. instance = new LanHan();
  9. }
  10. return instance;
  11. }
  12. }

Double CheckLock

线程安全

  1. public class DoubleLock {
  2. private volatile static DoubleLock instance;
  3. private DoubleLock() {
  4. }
  5. public static DoubleLock newInstance() {
  6. if (instance == null) {
  7. synchronized (DoubleLock.class) {
  8. // 防止并发问题,再判断一次
  9. if (instance == null) {
  10. instance = new DoubleLock();
  11. }
  12. }
  13. }
  14. return instance;
  15. }
  16. }

静态内部类实现模式

线程安全,调用效率高,可以延时加载

  1. public class StaticInner {
  2. private static class SingletonClassInstance {
  3. private static final StaticInner instance = new StaticInner();
  4. }
  5. private StaticInner() {
  6. }
  7. public static StaticInner getInstance() {
  8. return SingletonClassInstance.instance;
  9. }
  10. }

枚举实现

线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用

  1. public enum EnumMethod {
  2. /**
  3. * 枚举元素本身就是单例
  4. */
  5. INSTANCE;
  6. /**
  7. * 自定义操作
  8. */
  9. public void singletonOperation() {
  10. }
  11. }

工厂模式

工厂模式可以分为3类:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

    工厂模式的优点

  • 解耦:将对象的创建和使用进行分离

  • 可复用:对于创建过程比较复杂且在很多地方都使用到的对象,通过工厂模式可以提高对象创建的代码的复用性。
  • 降低成本:由于复杂对象通过工厂进行统一管理,所以只需要修改工厂内部的对象创建过程即可维护对象,从而达到降低成本的目的。

    简单工厂

  • 何时使用简单工程模式?

    • 需要创建的对象少
    • 客户端不需要关注对象的创建过程
  • 优点
    • 调用者想创建一个对象,只需要知道其名称即可
  • 缺点
    • 违背开闭原则,每增加一个对象都需要修改工厂类。
  • 总结
    简单工厂模式代码简单,虽有多处if分支且违背开闭原则,但扩展性和可读性尚可,这样的代码在大多数情况下并无问题。

    工厂方法

    通过不同的工厂来创建不同的对象,每个对象有对应的工厂创建。

  • 何时使用工厂方法模式?

    • 一个类不需要知道所需对象的类,只需要知道具体类对应的工厂类。
    • 一个类通过其子类来决定创建哪个对象,工厂类只需提供一个创建对象的接口。
    • 将创建对象的任务委托给具体工厂,也可以动态指定由哪个工厂的子类创建。
  • 简单工厂模式和工厂方法模式对比
    当对象的创建过程比较复杂,需要组合其他类对象做各种初始化操作时,推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中使得每个工厂类不过于复杂。而使用简单工厂模式则会将所有的创建逻辑都放到一个工厂类,会导致工厂类过于复杂。
  • 优点
    • 调用者想创建一个对象,只需要知道其名称即可。
    • 扩展性高,如果增加一个类,只需要扩展一个工厂类。
    • 对调用者屏蔽对象具体实现,调用者只需要关注接口。
  • 缺点

    • 当增加一个具体类时,需要增加其对应的工厂类,在一定程度上增加了系统的复杂度。

      抽象工厂

      工厂不仅可创建一种对象,还可创建一组对象
  • 何时使用抽象工厂模式?

    • 需要一组对象完成某种功能或多组对象完成不同的功能。
    • 系统稳定,不会额外增加对象
  • 优点
    • 扩展性高,可通过一组对象实现某个功能
  • 缺点

    • 一旦增加就需要修改原有代码,不符合开闭原则,所以尽可能用在不需要修改的场景。

      设计原则

      单一原则

      定义:一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;

      里式替换原则

      定义:所有引用基类的地方必须能透明地使用其子类的对象;
      里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
      里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:
  • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。

  • 子类中可以增加自己特有的方法。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比- 父类方法的输入参数更宽松。(即只能重载不能重写)
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

    依赖倒置原则

    定义:面向接口编程,上层模块不应该依赖下层模块,两者应依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;

    接口隔离原则

    定义:建立单一接口,客户端不应该依赖它不需要的接口;类之间依赖关系应该建立在最小的接口上;

    迪米特原则

    定义:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

    开闭原则

    定义:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。