通俗易懂的23种设计模式

什么是设计模式

设计模式(Design Pattern)是对代码开发经验的总结,是解决特定问题的一系列套路,并不是语法规定,而是一套用于提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。(重点面向对象 OOP)

学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

正确使用设计模式具有以下有点:

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件开发周期。
  • 是设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

23 种设计模式(GoF23 是一种思维,一种态度,一种进步)

创建型模式

单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

结构型模式

适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式

模板方法模式、命令模式、迭代器模式、观察者模式、中介模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

OOP 七大原则

开闭原则:对拓展开放,对修改关闭

里氏替换原则:继承必须确保超类所拥有的性质咋子类中仍然成立(即正方形不是长方形,尽量不重写父类的方法)

依赖倒置原则:要面向接口编程,不用面向实现编程

单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性(原子性,一个方法尽量做一件事)

接口隔离原则:要为各个类建立他们所需要的专用接口

迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话

合成复用原则:尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现

创建型模式

单例模式

核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

详细介绍:彻底玩转单例模式

常见场景:

  • Windows 的任务管理器
  • Windows 的回收站
  • 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去 new 对象读取
  • 网站的计数器一般也会采用单例模式,可以保证同步
  • 数据库连接池的设计一般也是单例模式
  • 在 servlet 编程中,每个 servlet 也是单例的
  • 在 spring 中,每个 bean 默认就是单例的

工厂模式

作用:实现了创建者和调用者的分离

详细分类:简单工厂模式、工厂方法模式、抽象工厂模式

核心本质:

  • 实例化对象不使用 new,用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

简单工厂模式(静态工厂模式)

用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖(拓展)已有代码)

简单工厂模式虽然某种程度上不符合设计原则,但实际应用最多。

工厂方法模式

用于生产同一等级结构中的固定产品(支持增加任意产品)

工厂方法模式,不修改已有类的前提下,通过增加新的工厂类实现扩展。

抽象工厂模式

围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。不可增加产品,但可增加产品族。

定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体得类

适用场景:

  • 客户端(应用层)不依赖于产品类型示例如何被创建、实现等细节
  • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现

优点:

  • 具体产品在应用层的代码隔离,无需关心创建的细节
  • 将一系列的产品同一到一起创建

缺点:

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难
  • 增加了系统的抽象性和理解难度

img

工厂模式的应用场景:

  • JDK 中 Calendar 的 getInstance 方法
  • JDBC 中的 Connection 对象的获取
  • Spring 中 IOC 容器创建管理 Bean 对象
  • 反射中 Class 对象的 newInstance 方法

建造者模式

建造者模式也属于创建者模式,它提供了一种创建对象的最佳方式。

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

例子:

工厂(建造者模式):负责制造汽车(组装过程和细节在工厂)

汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(其中不需要知道汽车时怎么组装的)

优点:

  • 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。
  • 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
  • 具体的建造者类直接时相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合”开闭原则“。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

应用场景:

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
  • 适合于一个具有较多的零件(属性)的产品(对象)的创建过程。

建造者和抽象工厂模式的比较:

  • 与抽象工厂模式相比,建造者模式放回一个组装好的完整产品,从而抽象工厂模式放回一系列相关的产品,这些产品位于不同的产品等级结构,构成一个产品族。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用过程方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组成工厂,通过对部件的组装可以返回一辆完整的汽车!

原型模式

定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

优点:

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

结构型模式

从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。

适配器模式

将一个类的接口转换成客户希望的另一个接口。Adapter 模式使得原本由于接口不兼容而不能一个工作的那些类可以在一起工作。

角色分析

目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。

需要适配的类:需要适配的类或适配者类。

适配器:通过包装一个需要适配的对象,把原接口转换成目标对象。

对象适配器优点

  • 一个对象适配器可以把多个不同的适配者适配到一个目标
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可以通过该适配器进行适配。

类适配器缺点

  • 对于 Java、C#等不支持多重类集成的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  • 在 Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

类适配器

组合

适用场景

  • 系统需要适用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

桥接模式

桥接模式是将抽象部分与它的实现部分分离,使他们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(interface)模式。

优点:

  • 桥接模式偶尔类似与多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案根好的解决方法。记得的减少了子类的个数,从而降低管理和维护成本。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原创,就像一座桥,可以把两个变化的维度链接起来。

缺点:

  • 桥接模式的引入会增加系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。

最佳实践:

  • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使他们在抽象层建立一个关联关系。抽象化角色和实现角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,其这两个维度都需要进行扩展。
  • 虽然在系统中使用继承时没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计需求需要独立管理这两者。对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

场景:

  • Java 语言通过 Java 虚拟机实现了平台的无关性。
  • AWT 中 Peer 架构
  • JDBC 驱动程序也是桥接模式的应用之一。

代理模式

静态代理

动态代理