当前位置: 首页 > ai >正文

设计模式 | 常见的设计模式(单例、工厂、代理、适配器、责任链等等)

        

目录

单例模式

饿汉模式

懒汉模式

懒汉模式改进-双重校验锁

工厂模式

代理模式

代理模式的主要角色

静态代理

动态代理

适配器模式

类适配器

对象适配器

策略模式

责任链模式

模板模式


设计模式是软件开发中被反复使用、经过验证、解决特定问题的代码结构和设计方法。设计模式提供了一套标准化的解决方案,用于应对面向对象软件设计中的常见问题。本篇博客将介绍单例模式、工厂模式、代理模式、适配器模式、策略模式、责任链模式、模板模式。

单例模式

        单例模式是一种创建型设计模式,它确保一个类只能有一个实例,并提供一个全局访问点来访问该实例。这在需要确保某个对象只能有一个实例的情况下非常有用,例如数据库连接池的配置。单例模式最常见的实现方式有饿汉模式和懒汉模式。

饿汉模式

        饿汉模式在类加载时就创建了对象的实例,因此是线程安全的。但如果实例的创建比较耗费资源且这个实例我们并不一定会用到,就会造成资源浪费。其通过构造函数私有化确保外部对象不能创建该类的实例,具体的实现方式如下所示:

/*** 饿汉模式*/
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance(){return instance;}
}

懒汉模式

        懒汉模式在类加载时不创建实例,而是在第一次获取实例时来进行对象创建的,具体实现如下所示:

public class Singleton {private static Singleton instance;private Singleton(){}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

        如果是在单线程环境下,不需要对getInstance方法加锁,因为不涉及到线程安全问题;如果是在多线程环境下,如果不给getInstance方法加锁就会引发线程安全问题,这个线程安全问题其实主要发生在首次创建实例时。在首次创建实例时,如果多个线程同时调用getInstance就可能会导致创建出多个实例,这就是线程安全问题。但是一旦实例已经创建好,后面在多线程环境调用getInstance方法就不会出现线程安全问题。

懒汉模式改进-双重校验锁

        双重检验锁是在懒汉模式的基础上,使用双重if来判定,降低了锁竞争的概率,并且给instance加了volatile来修饰。具体实现代码如下所示:

public class Singleton {private static volatile Singleton instance;private Singleton(){}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

        加锁/解锁是一件开销比较高的操作。然而懒汉模式的线程不安全问题其实仅出现在首次获取实例时创建实例那会,因此在后续使用的时候,就不用给整个getInstance方法加锁了。

        外层的if就是判定下看当前是否已经把instance实例创建出来了。同时在instance补充了volatile关键字,避免了因为“内存可见性”导致的读取instance出现偏差的情况。

        当多个线程首次调用getInstance方法,此时大家都会发现instance为null,于是这些线程就开始竞争锁,其中竞争成功的线程就会完成实例的创建。当这个线程执行结束后,其他线程开始竞争锁,此时竞争到锁的线程就被里层的if判定挡住了,也就不会创建其它实例了。

工厂模式

        工厂模式将对象的创建过程封装起来,与对象的使用过程分离。 客户端(调用者)不关心对象是如何被创建出来的,只需要从“工厂”中获取即可。通过定义一个创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类,如下列代码为例:

// 产品接口
interface Phone{void make();
}
// 具体产品
class Iphone implements Phone{@Overridepublic void make() {System.out.println("生产一部苹果手机");}
}
// 具体产品
class HuaWeiPhone implements Phone {@Overridepublic void make() {System.out.println("生产一部华为手机");}
}
// 工厂接口
interface PhoneFactory{Phone createPhone(); // 工厂方法
}
//具体工厂
class IPhoneFactory implements PhoneFactory {@Overridepublic Phone createPhone() {return new Iphone();}
}//具体工厂
class HuaweiPhoneFactory implements PhoneFactory {@Overridepublic Phone createPhone() {return new HuaWeiPhone();}
}
public class FactoryPatternDemo {public static void main(String[] args) {// 想要iphone就找,iphone工厂PhoneFactory iphoneFactory = new IPhoneFactory();Phone iphone = iphoneFactory.createPhone();iphone.make();// 想要huawei,就找huawei工厂PhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();Phone huawei = huaweiPhoneFactory.createPhone();huawei.make();}
}

        如果此时我们想要增加一个新的产品小米手机,只需要增加一个小米手机的具体工厂类即可,无需修改任何现有工厂类的代码。

        抽象工厂模式提供一个创建一系列相关或相互依赖的对象接口,而无需指定它们的具体实现类。它通常用于创建一组相关的产品。

        工厂模式与抽象工厂模式的区别:

        · 工厂模式关注单个产品的创建,通过子类决定实例化哪个产品,是用于创建单个产品对象。即一个抽象工厂,多个具体工厂,每个具体工厂生产一个产品。

        · 抽象工厂模式关注一系列相关或相互依赖的产品创建,提供一个接口来创建多个相关的产品对象,适用于创建一组相关的产品对象,确保它们之间的兼容性。即一个抽象工厂,多个具体工厂,每个工厂生产一系列相关产品。

代理模式

        代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不改变原始对象的情况下,通过代理对象来控制对原始对象的访问。也就是提供一个代理对象来控制对另一个真实对象的访问。客户端不再直接和真实对象打交道,而是通过代理这个“中介”来间接访问。

代理模式的主要角色

        · 抽象主题(Subject):定义了真是主题和代理的共同接口,这样代理就可以在任何时候替代真实主题。

        · 真实主题(RealSubject):实现了抽象主题,定义了代理所代表的真实对象。

        · 代理(Proxy):持有真实主题的引用,并且实现了抽象主题接口。代理可以在调用真实主题方法前后进行一些额外的操作。

静态代理

        静态代理在编译时就确定了代理类是谁,是在编译时由程序员创建或使用工具自动生成的代理类。代理类和真实主题类都实现相同的接口,代理类在调用委托类(真是主题类)的方法前后都可以添加一些额外的逻辑。

        其优点是简单直观,容易理解和实现,在编译时确定代理类,性能较高;缺点是每个代理类都需要手动编写,增加了代码量,一旦接口发生变化,代理类也需要修改,维护成本较高。

        代码示例:以“电影播放场景”为例,利用代理类在播放前后插入广告

// 抽象主题
interface Movie {void play();
}
// 真实主题
class RealMovie implements Movie {@Overridepublic void play() {System.out.println("正在播放电影《爵迹》");}
}class MovieProxy implements Movie { // 代理也实现同样的接口private RealMovie realMovie; // 持有真实对象的引用public MovieProxy(RealMovie realMovie) {this.realMovie = realMovie;}@Overridepublic void play() {// 前置处理System.out.println("[代理] 播放前先来一段广告~");// 核心处理realMovie.play();// 后置处理System.out.println("[代理] 播放后再来一段广告~");}
}public class StaticProxyDemo {public static void main(String[] args) {// 创建真实对象RealMovie realMovie = new RealMovie();// 创建代理对象,并将真实对象注入MovieProxy movieProxy = new MovieProxy(realMovie);// 客户端只和代理打交道,不知道真实对象的存在movieProxy.play();}
}

输出结果为:

        如果一个真实主题有非常多的方法,那么代理类也需要实现同样多的方法,即使增强的逻辑是一样的,也会导致代码非常冗余。

动态代理

        动态代理是在运行时创建代理类及其对象的。程序员不需要手动创建,Java本身提供了支持(主要利用反射机制)。Java中常见的实现方式一种是JDK动态代理: 基于接口实现java.lang.reflect.Proxy 类和 InvocationHandler 接口;另一种是CGLib 动态代理:基于子类实现(通过字节码技术),它可以代理没有实现接口的类。

        其优点是动态生成代理类,减少了代码量,接口变更时无需修改代理类,维护成本低;缺点是由于在运行时生成代理类,性能可能稍逊于静态代理,代码相对复杂,理解和调试难度较高。

        以JDK动态代理为例:

// 抽象主题
interface Movie {void play();
}
// 真实主题
class RealMovie implements Movie {@Overridepublic void play() {System.out.println("正在播放电影《爵迹》");}
}// 调用处理器
class MyInvocationHandler implements InvocationHandler {private Object target; // 被代理的真实对象public MyInvocationHandler(Object target) {this.target = target;}// 所有对代理对象的方法调用,都会转发到这个方法里来@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置处理System.out.println("[代理] 播放前先来一段广告~");// 调用真实对象的方法Object result = method.invoke(target, args);// 后置处理System.out.println("[代理] 播放后再来一段广告~");return result;}
}public class StaticProxyDemo {public static void main(String[] args) {// 创建真实对象RealMovie realMovie = new RealMovie();// 创建InvocationHandler,并传入真实对象InvocationHandler handler = new MyInvocationHandler(realMovie);// 动态的创建代理对象Movie proxy = (Movie) Proxy.newProxyInstance(RealMovie.class.getClassLoader(), // 类加载器new Class[]{Movie.class}, // 代理类要实现的接口列表handler // 调用处理器);// 通过代理对象调用方法proxy.play();}
}

运行结果如下所示:

        动态代理非常灵活,一个 InvocationHandler 可以代理各种不同类型的对象,无论真实主题有多少个方法,增强逻辑都写在一处(invoke 方法中),大大减少了代码量。

适配器模式

        适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户端希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以协同工作。它有两种实现方式:类适配器和对象适配器。类适配器通过继承实现接口转换,对象适配器通过组合实现接口转换。适配器模式在现实中有广泛的应用,例如将旧的系统与新的系统集成,使得不同的系统组件可以系统工作。

        适配器模式通常包含三个角色:目标接口、被适配者、适配器。

        · 目标接口:即客户端期望使用的方法,例如华为手机期望使用TypeC接口的充电器进行充电

        · 被适配者:已经存在的、需要被复用的类或组件,但其接口与目标接口不兼容,例如目前有usb接口的充电线,但其与需要的TypeC接口不一致

        · 适配器:模式的核心。它实现了目标接口,并内部持有一个被适配者的引用,适配器通过调用被适配者的方法,并对其进行转换,从而满足目标接口的要求。例如转接头,就是一个适配器。

主要目的

        · 解决兼容性问题:让两个不兼容的接口能够协同工作,将比如手机转接头一样,给usb接口装上一个适配器,就可以给TypeC接口的手机充电了。

        · 复用现有类:通过适配器模式,可以复用现有的类,而不需要修改它们的代码。

        · 解耦:将客户端与被适配的类解耦,客户端只面向目标接口编程,无需关心底层的适配细节。

        适配器模式的实现主要有类适配器和对象适配器。

类适配器

        类适配器通过继承被适配者来实现适配,类适配器使用多重继承来适配接口(在Java中通过实现接口和继承类来模拟多重继承)。

// 目标接口(Target): 客户端期望的接口
interface TypeC {void request();
}// 被适配者(Adaptee): USB接口,不兼容的类
class USB {public void specialRequest() {System.out.println("USB 接口");}
}// 类适配器: 通过继承被适配者
class ClassAdapter extends USB implements TypeC {@Overridepublic void request() {super.specialRequest();System.out.println("(类适配器)将 USB 接口转换为 TypeC接口");}
}// 客户端,华为手机需要TypeC接口充电
public class AdapterPatternDemo { // 客户端只依赖目标接口public static void main(String[] args) {TypeC typeC = new ClassAdapter();typeC.request();}
}

        运行结果如下所示:

对象适配器

        对象适配器使用组合的方式,将适配对象作为适配器的一个成员变量。

// 目标接口(Target): 客户端期望的接口
interface TypeC {void request();
}// 被适配者(Adaptee): USB接口,不兼容的类
class USB {public void specialRequest() {System.out.println("USB 接口");}
}// 对象适配器, 持有被适配者的对象, 实现目标接口
class ClassAdapter implements TypeC {// 核心: 持有一个被适配者的实例private USB usb;// 通过构造函数注入被适配对象public ClassAdapter(USB usb) {this.usb = usb;}@Overridepublic void request() {usb.specialRequest();System.out.println("(类适配器)将 USB 接口转换为 TypeC接口");}
}
// 客户端,华为手机需要TypeC接口充电
public class AdapterPatternDemo { // 客户端只依赖目标接口public static void main(String[] args) {USB usb = new USB();TypeC typeC = new ClassAdapter(usb);typeC.request();}
}

运行结果为: 

类适配器和对象适配器的区别:

        · 实现方式不同:类适配器是通过继承被继承者类并实现目标接口来实现的;而对象适配器是通过组合的方式实现的,在对象适配器内部持有一个被适配者类的对象引用并实现了目标接口。

        · 灵活性:类适配器的灵活性较低,只能适配一个特定的类及其子类,因为Java是单继承;而对象适配器的灵活性非常高,可以适配一个类及其所有子类,甚至可以同时适配多个不同的类。

        · 覆盖行为:类适配器可以重写被适配者的方法,因为它是子类;而对象适配器不能直接重写被适配者的方法,但可以通过包装和调用新的方法来扩展行为。

策略模式

        策略模式是一种行为型设计模式,主要是将一些共性的方法进行抽取,并提供一个可以进行决策选择的方法,从而进行解耦的快速开发。主要包括了策略接口,具体策略类和上下文类。

        以我在项目中的应用为例,在项目中,主要使用策略模式和责任链模式来实现抽奖后的状态扭转行为,主要包括了奖品状态扭转、抽奖人员状态扭转和活动状态扭转。在进行方案设计时,考虑到状态扭转条件(比如针对抽奖人员进行分类,第一类抽完才能抽取第二类这种等)可能会扩展,如果不使用策略模式,就需要写很多的if-else语句或者switch-case语句,会导致代码难以阅读和维护。因此,在设计时采用了策略模式,将状态扭转这里的逻辑抽象为公共方法,相当于针对状态扭转建立了一个抽象类,通过每种扭转类型来作为决策条件,然后通过不同的决策条件就可以定位到实际的状态扭转逻辑。

        这样做的好处主要是实现了解耦,可以实现每一种状态扭转对应一个状态扭转处理类,后续如果要添加新的状态扭转类型只要添加对应的状态扭转处理类即可。我在项目中使用的策略模式除了这种解耦操作,我还基于策略模式设计了其状态扭转的一个处理顺序。例如,我们的活动状态扭转必须是人员状态扭转和奖品状态扭转完成之后才能对活动状态扭转,为了实现这一功能,我定义了一个sequence方法用来决定不同状态扭转的一个顺序。对于人员状态扭转和奖品状态扭转,不需要顺序,因为其没有先后顺序,而活动状态扭转必须在前两者完成后才能进行,因此在这一块的策略就是人员状态扭转和奖品状态扭转的sequence方法返回1,而活动状态扭转的sequence方法返回2,就可以基于策略模式实现这个状态扭转的顺序性。

代码中的具体实现

首先将状态扭转这个行为抽象为了一个公共类AbstractActivityOperator

public abstract class AbstractActivityOperator {/*** 控制处理顺序* @return*/public abstract Integer sequence();/*** 是否需要转换** @param convertActivityStatusDTO* @return*/public abstract Boolean needConvert(ConvertActivityStatusDTO convertActivityStatusDTO);/*** 转换方法** @param convertActivityStatusDTO* @return*/public abstract Boolean convert(ConvertActivityStatusDTO convertActivityStatusDTO);
}

        然后针对于三种不同的扭转行为了定义了三个类来继承这个抽象类,也就是具体的策略类,分别为ActivityOperator、PrizeOperator和UserOperator,具体的实现这里就不在赘述,也就是具体扭转逻辑的一个实现。

        下列代码通过Spring的依赖注入自动收集了所有AbstractActivityOperator的实现类,并且按照bean名称进行映射,键值对的形式为:<bean名称,bean实例>。

    @Autowiredprivate final Map<String, AbstractActivityOperator> operatorMap = new HashMap<>();

        策略模式的优点:无需修改代码上下文,就可以添加一个新的逻辑进来,避免了使用多重条件判断,提高了代码的灵活性和可复用性。

责任链模式

        责任链模式是一种行为型设计模式,它允许多个对象有机会处理请求,从而避免请求的接收者和发送者之间的耦合。将这些对象连成一条链,并沿着这条链处理请求,直到有对象处理它为止。

        以我在项目中的使用为例,此处的请求可以说就是活动状态的一个扭转,我们的抽奖是以奖品为维度进行抽奖的,我在设计时考虑的是第一次先处理人员和奖品状态的扭转,然后等这个两状态扭转完成后第二次再去扭转活动的状态,因为这是我对于抽奖系统的一个规范,必须这么做,采用责任链模式可以更好地实现这一功能。相当于就是策略模式讲的多个对象有机会处理请求,然后将这两个处理连成一条链,结合我在策略模式中对于执行顺序的这个定义,我此处只需要写一个方法就行,不需要写两种方法来处理不同的状态。

具体的实现如下所示

    @Override@Transactional(rollbackFor = Exception.class)public void handlerEvent(ConvertActivityStatusDTO convertActivityStatusDTO) {// 1、活动状态扭转有依赖性,导致代码维护性差// 2、状态扭转条件可能会扩展,当前写法扩展性差、维护性差if (CollectionUtils.isEmpty(operatorMap)) { // operatorMap 为空,表示没人员、奖品、活动的处理方法,因此直接返回logger.warn("operatorMap 为空! ");return;}Map<String, AbstractActivityOperator> currentMap = new HashMap<>(operatorMap);Boolean update = false;// 先处理: 人员、奖品update = processConvertStatus(currentMap, convertActivityStatusDTO, 1);// 后处理: 活动update = processConvertStatus(currentMap, convertActivityStatusDTO, 2) || update;// 更新缓存if (update) {activityService.cacheActivity(convertActivityStatusDTO.getActivityId());}}

        责任链模式的优点:降低了耦合度、增强了灵活性。

模板模式

        模板模式是一种行为设计模式,它定义了一个操作中算法的骨架,而将一些具体的步骤延迟到子类中。通过这种方式,子类可以在不改变算法结构的情况下重新定义算法中的某些步骤。简单来说就是父类定义流程,子类实现细节。

        假设有一个制作饮品的系统,不同的饮品有不同的制作步骤,这里以泡茶和泡咖啡为例,这里的操作我们假设可以分为:烧水、取茶叶/取咖啡、将茶叶/咖啡放入杯子中、将水倒入杯子中这四个步骤。我们可以将这四步定义一个算法骨架,包括boilWater()、getCondiment()、putCondiment()和putWaterToCup(),其中boilWater()和putWaterToCup()都是两者共有的操作,可以直接实现,而另外两个操作我们可以写成抽象方法来让子类实现细节,具体代码如下所示: 

// 抽象类
abstract class Beverage {// 模板方法public final void prepareRecipe() {boilWater();getCondiment();putCondiment();putWaterToCup();}// 具体方法private void boilWater() {System.out.println("Boiling water");}// 抽象方法protected abstract void getCondiment();protected abstract void putCondiment();private void putWaterToCup() {System.out.println("Pouring into cup");}
}
class Tea extends Beverage {@Overrideprotected void getCondiment() {System.out.println("从茶罐里取茶");}@Overrideprotected void putCondiment() {System.out.println("将茶放入杯子中");}
}class Coffee extends Beverage {@Overrideprotected void getCondiment() {System.out.println("从咖啡罐中取咖啡");}@Overrideprotected void putCondiment() {System.out.println("将咖啡放入杯子中");}
}// 使用示例
public class TemplatePatternDemo {public static void main(String[] args) {Beverage tea = new Tea();tea.prepareRecipe();Beverage coffee = new Coffee();coffee.prepareRecipe();}
}

        运行结果为:

http://www.xdnf.cn/news/19366.html

相关文章:

  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day12
  • Langflow 多模态技术深度分析
  • Hysplit大气传输和污染扩散-轨迹聚合标准20%30%用途
  • OpenCV 图像直方图与对比度增强实战:从分析到优化
  • Week 14: 深度学习补遗:迁移学习
  • 《隐性质量:决定软件生命周期的看不见的竞争力》
  • Langflow Agents 技术深度分析
  • 极客学院-从零开始学架构
  • MCP SDK 示例一
  • Linux 特殊文件系统
  • 二、程序设计语言基础知识
  • 预售破 500 万!淮北吾悦广场京东奥莱8月29日开业燃动皖北
  • Pytest+Selenium4 Web自动化测试框架(三日速通)
  • ANR InputDispatching TimeOut超时判断 - android-15.0.0_r23
  • python如何打开显示svg图片
  • react-beautiful-dnd ​React 拖拽(Drag and Drop)库
  • Scikit-learn Python机器学习 - 类别特征提取- OneHotEncoder
  • 人工智能-python-深度学习-
  • RPC个人笔记(包含动态代理)
  • HarmonyOS 应用开发:基于API 12+的现代化开发实践
  • shell编程基础入门-2
  • 层次分析法
  • 现代C++特性 并发编程:线程管理库 <thread>(C++11)
  • dayjs 常用方法总结
  • MySQL—— 概述 SQL语句
  • MSVC---编译器工具链
  • 【CUDA入门·Lesson 1】Ubuntu实战:CUDA 概念、nvidia-smi 工具与 GPU 参数详解
  • Docker从零学习系列之Dockerfile
  • 蓓韵安禧活性叶酸独立包装防漏贴心设计
  • 策略模式:模拟八路军的抗日策略