本文是详解博主在工作中对常用设计模式的使用经验总结归纳而来分享给大家。 设计模式一共有23种,常用本文讲解涉及如下: 业界一般将设计模式分为三大类: 设计模式遵循了六大原则,也称为SOLID原则: 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,使多个对象都有机会处理请求。在这个模式中,请求沿着一个处理链依次传递,直到有一个对象能够处理它为止。 责任链模式的核心思想是将请求的发送者和接收者解耦,使得多个对象都有机会处理请求。在责任链模式中,请求会沿着一个处理链依次传递,每个处理者都有机会处理请求,如果一个处理者不能处理请求,则将请求传递给下一个处理者,直到有一个处理者能够处理它。 责任链模式包含以下几个角色: 优点: 缺点: 责任链模式在许多不同的应用场景中都有广泛的应用。下面列举了一些常见的应用场景: 在 Java 中实现责任链模式有多种方式,包括基于接口、基于抽象类、基于注解等。下面将详细介绍基于接口的常见实现方式。 基于接口的实现方式是通过定义一个处理请求的接口,每个处理者实现这个接口,并在自己的实现中决定是否处理请求和传递请求给下一个处理者。 首先,我们定义一个处理请求的接口 Handler 以及请求入参 Request: { ; } { String type; } 然后,我们创建3个具体的处理者类实现这个接口,在具体处理者类的实现中,首先判断自己是否能够处理请求,如果能够处理,则进行处理;否则将请求传递给下一个处理者。代码如下: { Handler successor; { .successor = successor; } { )) { ) { successor.handleRequest(request); } } } { Handler successor; { .successor = successor; } { )) { ) { successor.handleRequest(request); } } } { Handler successor; { .successor = successor; } { )) { ) { successor.handleRequest(request); } } } 接下来,我们创建一个客户端类 Client,用于创建处理者对象并组成责任链的结构: { { ConcreteHandlerA(); ConcreteHandlerB(); ConcreteHandlerC(); handlerA.setSuccessor(handlerB); handlerB.setSuccessor(handlerC); ); handlerA.handleRequest(request); } } 在客户端类中,我们创建了具体的处理者对象,并通过 setSuccessor() 方法将它们组成一个责任链的结构。然后,创建一个请求对象,并将请求发送给第一个处理者。 基于接口的实现方式简单直观,每个处理者只需要实现一个接口即可。但是它的缺点是如果责任链较长,需要创建多个处理者对象,增加了系统的复杂性和资源消耗。下面基于 Spring 框架实现一个责任链模式。 在实际开发中,一个请求会在多个处理器之间流转,每个处理器都可以处理请求。 假设我们有一个 Spring 框架开发的订单处理系统,订单需要依次经过订单检查、库存处理、支付处理。如果某个处理环节无法处理订单,将会终止处理并返回错误信息,只有每个处理器都完成了请求处理,这个订单才算法下单成功。 首先,我们定义一个订单类 Order: { String orderNumber; String paymentMethod; stockAvailability; String shippingAddress; } 然后,我们定义一个抽象订单处理者类 OrderHandler: { ; } 接下来,我们创建具体的订单处理者类继承自抽象订单处理者类,实现相应的方法,并注册到 Spring 中, { { (StringUtils.isBlank(order.getOrderNo())) { ); } ) { ); } (StringUtils.isBlank(order.getShippingAddress())) { ); } ); } } { { (!order.isStockAvailability()) { ); } ); } } { { )) { ); } ); } } 在具体订单处理者类的实现中, CheckOrderHandler 负责做订单参数检查、 StockHandler 负责做库存扣减、 AliPaymentHandler 负责做预下单,每个处理者的逻辑都是相互独立各不不干扰。 最后,我们创建一个订单生产链条 BuildOrderChain ,用于组成责任链的链条处理结构: { AliPaymentHandler aliPaymentHandler; CheckOrderHandler checkOrderHandler; StockHandler stockHandler; ArrayList<>(); { list.add(checkOrderHandler); list.add(stockHandler); list.add(aliPaymentHandler); } { .list) { orderHandler.handleOrder(order); } } } 订单生产链条 BuildOrderChain 类中,我们通过 @PostConstruct 注解下的 init() 初始化方法,将具体的订单处理者按代码顺序组成一个责任链的结构。然后通过 doFilter(order) 方法遍历处理者集合依次处理。 运行代码: 4j ) { BuildOrderChain buildOrderChain; { , )); buildOrderChain.doFilter(order); } } ------------------------------- 订单参数检验通过 库存扣减成功 支付宝预下单成功 可以看到订单依次经过校验处理器、库存处理器和支付处理器进行处理,直到最后完成整个订单的处理。 在举个例子,假如我们的订单针对的是虚拟不限库存商品,我们不需要进行库存扣减,那我们可以直接新建 VirtualGoodsOrderChain 虚拟商品订单生产链条类,代码如下, { AliPaymentHandler aliPaymentHandler; CheckOrderHandler checkOrderHandler; ArrayList<>(); { list.add(checkOrderHandler); list.add(aliPaymentHandler); } { .list) { orderHandler.handleOrder(order); } } } 运行代码: { )); virtualGoodsOrderChain.doFilter(order); } ------------------------------------------- 订单参数检验通过 支付宝预下单成功 总的来说,责任链模式适用于存在多个处理步骤、每个处理步骤具有独立逻辑或条件、需要灵活组合和扩展的场景。通过责任链模式,可以将复杂的处理逻辑拆分为多个独立的处理步骤,并且可以动态地组合和调整处理步骤的顺序,从而提高系统的灵活性和可维护性。希望本文能够帮助读者理解和应用责任链模式,提升软件设计和开发的能力。 模板方法模式是一种行为型设计模式,它定义一个操作(模板方法)的基本组合与控制流程,将一些步骤(抽象方法)推迟到子类中,在使用时调用不同的子类,就可以达到不改变一个操作的基本流程情况下,即可修改其中的某些特定步骤。这种设计方式将特定步骤的具体实现与操作流程分离开来,实现了代码的复用和扩展,从而提高代码质量和可维护性。 模板方法模式包含以下: 模板方法模式的缺点: 如上,我们用一个简单的发送短信代码来做模板方法模式的示例: 定义一个发送短信模板 /** * 发送短信模板 { /** * 发送方法 * mobile 手机号 { "检查用户一分钟内是否发送过短信, + mobile); (checkUserReceiveInOneMinute(mobile)) { ); } String code = genCode(); (manufacturer(mobile, code)) { "短信厂商发送短信成功, + code); save2redis(mobile, code); } } /** * 模板方法,由不同的厂商来实现发送短信到手机上 ; /** * 检查1分钟内该手机号是否接收过验证码,1分钟内接收过就不能在发送验证码 mobile { ...; } /** * 生成6位验证码 { ; } /** * 将手机号+验证码存进redis中,给登录接口做校验用 mobile code { ... } } 添加两个不同厂商实现的子类 /** * 阿里云短信发送 { { ); ); ); ; } } /** * 腾讯云短信发送 { { ); ); ); ; } } 在 Java 程序中进行调用 { { AliyunSmsSend(); ); ); TencentSmsSend(); ); } } 输出如下: 检查用户一分钟内是否发送过短信,mobile:13333333333 读取阿里云短信配置 创建阿里云发送短信客户端 阿里云发送短信成功 短信厂商发送短信成功,mobile:13333333333,code=123456 --------------------------- 检查用户一分钟内是否发送过短信,mobile:13333333333 读取腾讯云短信配置 创建腾讯云发送短信客户端 腾讯云发送短信成功 短信厂商发送短信成功,mobile:13333333333,code=123456 我们来看看模板方法模式的组成: 在 Spring 中实现模板方法模式,是非常简单的,我们只需要对上述的 Java 代码示例的 AliyunSmsSend 类稍作改造,加上 @Component 注解就行, /** * 阿里云短信发送 { { ; ); ); ); ; } } 如果在 AliyunSmsSend 类中需要注入其他 bean,通过 cn.hutool.extra.spring.SpringUtil.getBean(...) 方法获取对应 bean 就行。 在Java8 中,还可以使用函数表达式来替换抽象方法,代码如下, /** * 发送短信模板 { /** * 发送短信 mobile 手机号 biFunction Exception (String mobile, { + mobile); (checkUserReceiveInOneMinute(mobile)) { ); } String code = genCode(); (biFunction.apply(mobile, code)) { + code); save2redis(mobile, code); } } ... } 通过 BiFunction 函数,将不同厂商发送短信到用户手机的代码在 send(mobile) 方法中分离处理。 调用方法如下: { SmsTemplateLambda(); , (s, s2) -> { ); ); ); ; }); , (s, s2) -> { ); ); ); ; }); } 可以看到,我们可以只在调用 SmsTemplateLambda 类的 send(mobile) 方法时,才实现不同厂商发送短信到手机的具体逻辑。好处就是每增加一个模板方法时,不用增加具体的子类实现,减少类的创建与降低子类的实现成本。 模板方法模式通过定义一个流程基本操作也就是模板方法,将具体的实现步骤推迟到子类中,使得子类可以灵活地实现可变的行为,这是模板方法模式的核心思想与价值所在。 订阅发布模式(Publish-Subscribe Pattern)是一种行之有效的解耦框架与业务逻辑的方式,也是一种常见的观察者设计模式,它被广泛应用于事件驱动架构中。 观察者模式的各角色定义如下。 优点: 缺点: { ; } { HashMap<>(); { List ) { ArrayList<>(); subscribers.put(topic, subscriberList); } subscriberList.add(subscriber); } { List ) { subscriberList.remove(subscriber); } } { List ) { (Subscriber subscriber : subscriberList) { subscriber.update(message); } } } } { String email; { .email = email; } { + message); } } { String phoneNumber; { .phoneNumber = phoneNumber; } { + message); } } { { Publisher(); ); ); , emailSubscriber1); , smsSubscriber1); ); , smsSubscriber1); ); } } 打印输出如下: Send email to foo@example.com: 发布新消息1 Send SMS to 1234567890: 发布新消息1 Send email to foo@example.com: 发布新消息2 Spring的订阅发布模式是通过发布事件、事件监听器和事件发布器3个部分来完成的 这里我们通过 「newbee-mall-pro」项目中已经实现订阅发布模式的下单流程给大家讲解,项目地址: https://github.com/wayn111/newbee-mall-pro { { ... } } { { ... } } ApplicationEventPublisher applicationEventPublisher; { ... String orderNo = NumberUtil.genOrderNo(); OrderEvent(orderNo, mallUserVO, couponUserId, shopcatVOList)); orderNo; ... } 通过事件监听机制,我们将下单逻辑拆分成如下步骤: 每个步骤都是各自独立 如上的代码已经实现了订阅发布模式,成功解耦了下单逻辑。建议大家在日常开发中多加思考哪些业务流程可以适用,例如微服务项目中订单支付成功后需要通知用户、商品、活动等多个服务时,可以考虑使用订阅发布模式。解耦发布者和订阅者,发布者只管发布消息,不需要知道有哪些订阅者,也不需要知道订阅者的具体实现。订阅者只需要关注自己感兴趣的消息即可。这种松耦合的设计使得系统更容易扩展和维护。 策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端(即算法的调用者)。 策略模式的各角色定义如下。 优点: 缺点: 策略模式适用于以下场景: 假设我们有一个计算器程序,它可以根据用户输入的不同运算符(+、-、*、/)来执行不同的算术运算。我们可以使用策略模式来实现这个功能,具体步骤如下: 我们首先定义一个策略接口 Strategy ,它声明了一个 doOperation 方法,用于执行具体的运算。 { ; } 然后我们实现四个具体的策略类,分别是 AddStrategy 、SubtractStrategy 、MultiplyStrategy 和 DivideStrategy ,它们都实现了 Strategy 接口,并重写了 doOperation 方法。 { { num1 + num2; } } { { num1 - num2; } } { { num1 * num2; } } { { ) { ); } num1 / num2; } } 接下来我们定义一个上下文类 Context ,它持有一个 Strategy 的引用,并提供了一个构造方法和一个 executeStrategy 方法。构造方法用于传入具体的策略对象,executeStrategy 方法用于调用策略对象的 doOperation 方法。 { Strategy strategy; { .strategy = strategy; } { strategy.doOperation(num1, num2); } } 最后我们编写一个测试类,用于创建不同的策略对象和上下文对象,并根据用户的输入来执行不同的算法。 { { Scanner(System.in); ); num1 = scanner.nextInt(); ); String operator = scanner.next(); ); num2 = scanner.nextInt(); scanner.close(); Strategy strategy; (operator) { AddStrategy(); SubtractStrategy(); MultiplyStrategy(); DivideStrategy(); -> { ); ; } } Context(strategy); result = context.executeStrategy(num1, num2); + result); } } 请输入第一个数: 1 请输入运算符(+、-、*、/): + 请输入第二个数: 1 1 + 1 = 2 在 Spring 框架中,也有很多地方使用了策略模式,比如 BeanFactory 的实现类,它们都实现了一个 BeanFactory 接口,但是具体的实例化和管理 Bean 的方式不同。比如 XmlBeanFactory 是从 XML 文件中读取 Bean 的定义,而 AnnotationConfigApplicationContext 是从注解中读取 Bean 的定义。 我们可以使用 Spring 的依赖注入功能,来实现策略模式,具体步骤如下: 我们还是使用上面的计算器程序作为例子,首先定义一个策略接口 Strategy ,它声明了一个 doOperation 方法,用于执行具体的运算。 { ; } 然后我们实现四个具体的策略类,分别是 AddStrategy 、SubtractStrategy 、MultiplyStrategy 和 DivideStrategy ,它们都实现了 Strategy 接口,并重写了 doOperation 方法。同时,我们给每个策略类添加一个 @Component 注解,表示它们是 Spring 容器管理的组件。 { { num1 + num2; } } { { num1 - num2; } } { { num1 * num2; } } { { ) { ); } num1; } } 好的,我继续写。 接下来我们定义一个上下文类 Context ,它持有一个 Strategy 的引用,并提供了一个构造方法和一个 executeStrategy 方法。构造方法用于传入具体的策略对象,executeStrategy 方法用于调用策略对象的 doOperation 方法。同时,我们给上下文类添加一个 @Component 注解,表示它也是 Spring 容器管理的组件。 { Strategy strategy; { .strategy = strategy; } { strategy.doOperation(num1, num2); } } 为了让 Spring 容器能够自动注入不同的策略对象,我们需要使用 @Autowired 注解来标注 Context 类的构造方法,并使用 @Qualifier 注解来指定具体的策略类。这样,我们就可以根据不同的运算符来创建不同的上下文对象,而不需要手动创建策略对象。 { List { (Strategy strategy : list) { (strategy.getClass() == tClass) { strategy; } } ); } } 最后我们编写一个测试类,用于从 Spring 容器中获取 Context 对象,并根据用户的输入来执行不同的算法。 ) { Context context; { ); ; ); ; ); ; Strategy strategy; (operator) { ; ; ; ; -> { ); ; } } result = strategy.doOperation(num1, num2); + result); } } 请输入第一个数:2 请输入运算符(+、-、*、/):* 请输入第二个数:3 2 * 3 = 6 总的来说策略模式是一种常用的行为型设计模式,它可以将不同的算法封装在不同的类中,并让它们可以相互替换。策略模式可以避免使用多重条件语句,提高代码的可读性和可维护性,同时也可以实现开闭原则,增加系统的灵活性。但是策略模式也会增加客户端和系统的复杂度,因此需要根据具体的情况来权衡利弊。 至此本文所讲的四种常用设计模式就全部介绍完了,希望能对大家有所帮助。 关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,你的关注将是我的更新动力!三大分类
六大原则
设计模式的好处
可以重用设计,减少代码的重复,提高代码的可维护性。 可以为设计提供共同的词汇,方便程序员间的交流和理解。 可以实现开闭原则,增加新的功能或者修改旧的功能不影响原有的结构。 可以让重构系统变得容易,确保开发正确的代码,并降低出错的可能。 可以支持变化,为重写其他应用程序提供很好的系统架构。 后期可以节省大量时间,提高开发效率。 ❝ 1. 责任链模式
概述
责任链模式类
优缺点
应用场景
Java 代码示例
Spring 代码示例
2. 模板方法模式
概述
模板方法模式
优缺点
封装不变部分,扩展可变部分。模板方法模式将可变的部分封装在抽象方法中,不变的部分封装在基本方法中。这使得子类可以根据需求对可变部分进行扩展,而不变部分仍然保持不变。 避免重复代码,抽象类中包含的基本方法可以避免子类重复实现相同的代码逻辑。 更好的扩展性,由于具体实现由子类来完成,因此可以方便地扩展新的功能或变更实现方式,同时不影响模板方法本身。 应用场景
开发框架,通常框架会定义一些通用的模板,子类可以根据自身的特定需求来细化模板的实现细节,比如 Spring 中的 JdbcTemplate、RestTemplate、RabbitTemplate、KafkaTemplate 等。 业务逻辑,我们可以针对业务流程做一些拆解,将特定步骤改为子类实现。比如发送验证码的流程,在发送验证码时需要选择不同厂商来发送验证码,但是我们发送的验证码前的检查、验证码生成、保存验证码逻辑都是一样的。 Java 代码示例
Spring 代码示例
使用 Lambda 表达式
3. 订阅发布模式
概述
订阅发布模式
优缺点
应用场景
「构建实时消息系统」:比如普通的即时聊天,群聊等功能。发布者可以将消息发送到指定的频道或者主题,订阅者可以根据自己的兴趣或者身份来订阅不同的频道或者主题,并及时收到消息。 「实现事件驱动的系统」:比如前端框架中的事件监听和触发,或者后端框架中的中间件机制。发布者可以将事件作为消息发送出去,订阅者可以根据自己的业务逻辑来订阅不同的事件,并在事件发生时执行相应的操作。 「实现分布式系统中的消息队列」:比如使用 Redis、RabbitMQ、Kafka 等中间件来实现生产者和消费者之间的通信。发布者可以将任务或者数据作为消息发送到队列中,订阅者可以从队列中获取消息并进行处理。 「实现微信公众号等推送服务」:比如用户可以关注不同的公众号或者主题,并在有新内容时收到推送通知。发布者可以将内容作为消息发送到指定的公众号或者主题,订阅者可以根据自己的喜好来订阅不同的公众号或者主题,并在有新内容时收到推送通知。 Java 代码示例
创建订阅者接口,用于接受消息通知。 interface Subscriber Spring 代码示例
❝ 4. 策略模式
概述
策略模式
优缺点
应用场景
Java代码示例
Spring 代码示例
运行结果:
总结