改变编码游戏规则:揭秘23种设计模式的魅力和实用性
本文最后更新于:7 个月前
破冰
🍖 两万字盘点被玩烂了的9种设计模式 - 掘金 (juejin.cn)
👏 23种设计模式详解 - 掘金 (juejin.cn)
🍜 「聊设计模式」之抽象工厂模式(Abstract Factory) - 掘金 (juejin.cn)
什么是设计模式
- 设计模式是软件设计中常见的问题解决方案的归纳总结,是在特定情境下的经验性的解决方案。
- 它们被广泛接受和应用于软件开发中,旨在提高代码的可重用性、可维护性和灵活性。
七大原则
- 设计模式的七大原则是作为设计模式的基础准则,可以用来指导设计模式的选择和应用。这些原则是:
- 单一职责原则(SRP):一个类应该只有一个引起它变化的原因。
- 开放-封闭原则(OCP):软件实体(类、模块、函数等)应该对扩展是开放的,对修改是封闭的。
- 里氏替换原则(LSP):子类型必须能够替换其基类型,而不会影响程序的正确性。
- 接口隔离原则(ISP):建立最小的依赖,不要依赖不需要的接口。
- 依赖倒置原则(DIP):依赖于抽象,而不是具体实现。
- 迪米特法则(LoD):一个对象应该对其他对象保持最少的了解。
- 组合/聚合复用原则(CARP):优先使用组合和聚合,而不是继承。
分类
设计模式根据功能和用途可以分为三大分类:
- 创建型设计模式:这些模式关注对象的创建机制,主要包括工厂模式、抽象工厂模式、单例模式、原型模式和建造者模式等。
- 结构型设计模式:这些模式关注如何组合和使用对象,主要包括适配器模式、装饰器模式、代理模式、组合模式、享元模式和桥接模式等。
- 行为型设计模式:这些模式关注对象之间的通信和协作方式,主要包括策略模式、模板方法模式、观察者模式、迭代器模式、访问者模式、命令模式、备忘录模式、状态模式和解释器模式等。
这些分类和原则将设计模式划分到更具体的范畴,并提供了指导设计和实施设计模式的准则。理解和应用这些原则和分类有助于开发人员更好地使用设计模式来解决问题。
正文
单例模式
懒汉式
1 |
|
以上是标准的懒汉式实现单例模式,主要有三个步骤:(2023/09/19午)
- 静态变量
private static Singleton instance;
:
1
2
3- 在类的静态区域中创建一个私有的静态变量 `instance`,用于存储单例对象的实例。
- 静态变量保证了在类的所有实例中只存在一个副本。
- 这里将该静态变量设置为私有是为了确保无法通过类的外部直接访问和修改该变量。- 构造器私有化
private Singleton() {}
:
1
2- 将类的构造器私有化,防止通过 `new Singleton()` 在类的外部创建实例。
- 这样的设计强制要求只能通过类的内部调用来获取类的实例。- 对外暴露接口
public static Singleton getInstance()
:
1
2
3
4
5- 提供一个公共的静态方法 `getInstance()`,用于获取单例对象的实例。
- 在方法中,首先判断 `instance` 是否为空。
- 如果为空,即还未实例化对象,则在内部实例化一个对象,并将其赋值给 `instance`。
- 最后返回 `instance`,无论是首次调用还是后续调用,都返回同一个实例。
- 这样的设计实现了懒汉式的单例模式,只有在实际需要的时候才会创建对象,节省了资源。但是在多线程环境下,可能会存在线程安全问题,需要进行额外的处理来保证线程安全,如使用加锁机制、双重检查等。- 静态变量
饿汉式
1 |
|
- 这跟懒汉式唯一的区别就是:他提前把对象new出来,这样别人哪怕是第一次获取这个类对象的时候直接就存在这个类了,省去了创建类这一步的开销。
加锁优化(空间换时间)
1 |
|
- 这是一种典型的时间换空间的写法,不管三七二十一,每次创建实例时先锁起来,再进行判断,严重降低了系统的处理速度
双检锁优化
1 |
|
将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升
但是这样仍然会存在问题,创建一个实例分为三步骤:
- 分配内存空间、初始化对象、实例指向该内存空间
- 这样才算创建了一个实例
情景:
Volatile修饰
1 |
|
- 通过volatile修饰的变量,不会被线程本地缓存,所有线程对该对象的读写都会第一时间同步到主内存,从而保证多个线程间该对象的准确性
volatile的作用:
- 防止指令重排序,因为instance = new Singleton()不是原子操作
- 保证内存可见
静态内部类实现
1 |
|
使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕, 这样我们就不用担心上面的问题
同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题
枚举
1 |
|
适配器模式
我们有两种实现方式:
类适配器模式(Class Adapter Pattern):
- 在类适配器模式中,适配器类同时继承目标类和适配者类。通过继承适配者类,适配器类可以调用适配者类的方法,并通过实现目标接口,将适配者类的方法适配成目标接口的方法
对象适配器模式(Object Adapter Pattern):
- 在对象适配器模式中,适配器类持有一个适配者类的实例,并实现目标接口。通过调用适配者类的方法来实现目标接口的方法
以下是代码实现:
对象适配器模式
1 |
|
类适配器模式
1 |
|
- 实现起来,这两种方法没有多大区别
- 类适配器模式:通过继承,直接调用适配者的方法
- 对象适配器模式:通过创建一个适配者实例对象,使用这个对象调用适配者的方法
- 在 MemorySearch聚合搜索平台 项目中,我就使用到了适配器模式,用来统一处理前端请求 (2023/09/19午)
简单工厂模式
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
工厂方法模式
有了简单工厂,为什么还要有工厂方法呢?
如上,简单工厂是根据传递的参数选择地创建不同的对象实例,即所有的对象实例是在同一个工厂中创建的
如果需要创建的对象实例的类型有多种,我们不得不考虑为每一种对象实例定义一个专属的工厂,完成该对象实例的创建
我们仍沿用上面的产品类,通过定义一个具体的工厂来创建该产品对象实例(2023/09/22午)
定义一个抽象工厂
1 |
|
1 |
|
1 |
|
- 即,工厂方法与简单工厂的区别就是:工厂方法将工厂对对象实例的创建时机,延迟到了实现该工厂的具体子类中
抽象工厂模式
设想一下,如果要做到在一个具体的工厂中,同时创建大量的互相关联的对象,该如何做到呢?
答案就是在抽象工厂中,定义一组抽象的工厂方法,而具体工厂可以实现这些抽象方法,创建一系列相关的产品对象
定义一个抽象工厂,定义一组抽象工厂方法:(2023/09/22午)
1 |
|
1 |
|
1 |
|
1 |
|
策略模式
特点
- 策略模式是一种行为型设计模式,它通过定义一组算法家族,封装每个算法,并使它们可以相互替换,让算法的变换独立于使用算法的客户端
应用场景
- 对象有多种行为或算法,需要根据不同的情况选择不同的算法(2023/10/20早)
- 系统中有多个类实现相同的接口或继承相同的抽象类,但具体实现不同
- 需要在运行时动态地添加、删除或切换算法,而不影响客户端代码
- 一个类有多种变形或状态,每个状态有不同的行为,需要根据状态动态地改变对象的行为
代码实现
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
观察者模式
特点
- 观察者模式是一种行为型设计模式,定义了一种一对多的依赖关系 (2023/10/20早)
- 多个观察者可以同时监听同一主题对象,对象主题发生改变时,会通知所有观察者对象,使他们能够及时做出响应
- 观察者模式的核心是在主题对象和观察者对象之间建立一种松耦合的关系,以便于对象状态改变时通知观察者对象做出相应处理
代码实现
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
中介者模式
特点
- 中介者模式是一种行为型设计模式,它主要用于将关系很复杂的对象之间的通信进行解耦,让这些对象通过一个中介对象进行通信
- 降低对象之间的耦合度,使系统更加灵活和可扩展(2023/10/20早)
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
建造者模式
特点
- 建造者模式是一种对象创建型设计模式,它将复杂对象的构建过程分离出来,使得同样的构建过程可以创建不同的表示
- 该模式将构建对象的过程分为若干部分,分别进行构建,最终通过一个指挥者将这些部分组装成一个完整的对象
- 解决复杂对象的创建和表示问题,减少构建过程中的重复代码,提高代码的重用性和可维护性(2023/10/20早)
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
原型模式
特点
- 原型模式是一种通过复制现有对象来生成新对象的设计模式(2023/10/20早)
- 可以避免重复创建相似的对象,提高代码执行效率
- 动态地生成对象,避免在代码中硬编码对象的创建过程
- 可以实现对象的复用,减少对象的创建次数,降低系统开销
代码实现
1 |
|
1 |
|
1 |
|
命令模式
特点
- 命令模式是一种行为设计模式,它将请求分装成一个对象
- 命令模式的核心在于将请求和实现分离开,使请求具有独立的生命周期和实现 (2023/10/20早)
代码实现
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
桥接模式
特点
- 桥接模式是一种结构性设计模式,它将抽象和实现分开,以便它们可以独立地变化 (2023/10/20早)
- 在桥接模式中,有两个类层次结构:抽象类和实现类
- 当你想要改变一个类的实现方式而不想影响到客户端代码 / 需要将一个抽象类和它的实现分开时,推荐使用桥接模式
代码实现
装饰器模式
特点
- 装饰器模式是一种结构型设计模式,它允许我们在运行时扩展一个对象的功能,而不需要改变它的结构
- 在装饰器模式中,我们创建一个装饰器类,该类是西安一个与被装饰对象相同的接口,并且通过将被装饰对象作为参数传递到装饰器中,实现对被装饰对象的包装。这使得我们可以在不修改原始对象的基础上,在运行时动态地添加或修改对象的功能
- 装饰器模式常运用于:为对象添加缓存、日志记录、安全认证、性能优化等功能 (2023/10/20午)
代码实现
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
外观模式
特点
- 外观模式是一种结构性设计模式 (2023/10/20午)
- 在外观模式中,通常有一个外观类,封装了子系统中的一组接口,客户端只需要调用外观类提供的统一接口,就能够完成其所需功能
- 它隐藏了系统的复杂性,简化了客户端与子系统之间的交互,提高了系统的可维护性和可扩展性
- 当一个系统中有多个子系统,并且这些子系统之间互相合作以完成一个复杂的任务时,需要提供一个简单de接口来简化复杂的系统,推荐使用外观模式
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
代理模式
特点
- 代理模式是一种结构型设计模式,为其他对象提供一种代理,以控制对这些对象的访问,代理类与被代理类之间通过接口进行连接
- 代理模式可隐藏客户端真正调用的对象,实现代码解耦,增强系统的可维护性和可扩展性
- 代理模式常用于需要控制对对象的访问,并提供远程访问、安全检查和缓存等功能 (2023/10/20午)
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
享元模式
特点
- 享元模式是一种结构型设计模式,通过共享相同或相似的对象来减少内存消耗,提高系统的性能和效率 (2023/10/20午)
- 如果需要大量创建相同或相似对象,且创建和销毁对象的成本较高时,使用享元模式可以有效地优化系统性能和资源利用
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
责任链模式
特点
责任链模式是一种行为型模式,它将多个对象连成一条链,并将请求沿着链传递,直到有一个对象处理它为止
责任链模式将请求的发送者和接收者解耦,使得请求的发送者不需要知道请求的接收者是谁,也不需要知道请求是如何被处理的
责任链模式适用于这样的情况: (2023/10/20午)
- 多个对象都有机会处理请求,但是不知道哪个对象最终会处理请求
- 处理请求的对象集合可以动态配置,可以在运行时添加或删除处理对象
- 发送者不需要知道请求的处理细节,只需要知道请求会被处理
代码示例
1 |
|
1 |
|
1 |
|
1 |
|
状态模式
备忘录模式
迭代器模式
解释器模式
组合模式
模板方法模式