- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文是博主在工作中对常用设计模式的使用经验总结归纳而来分享给大家.
设计模式一共有23种,本文讲解涉及如下:
- 责任链模式
- 模板方法模式
- 发布订阅模式
- 策略模式
业界一般将设计模式分为三大类:
设计模式遵循了六大原则,也称为SOLID原则:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性.
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,使多个对象都有机会处理请求。在这个模式中,请求沿着一个处理链依次传递,直到有一个对象能够处理它为止.
责任链模式的核心思想是将请求的发送者和接收者解耦,使得多个对象都有机会处理请求。在责任链模式中,请求会沿着一个处理链依次传递,每个处理者都有机会处理请求,如果一个处理者不能处理请求,则将请求传递给下一个处理者,直到有一个处理者能够处理它.
责任链模式包含以下几个角色:
优点:
缺点:
责任链模式在许多不同的应用场景中都有广泛的应用。下面列举了一些常见的应用场景:
在 Java 中实现责任链模式有多种方式,包括基于接口、基于抽象类、基于注解等。下面将详细介绍基于接口的常见实现方式.
基于接口的实现方式是通过定义一个处理请求的接口,每个处理者实现这个接口,并在自己的实现中决定是否处理请求和传递请求给下一个处理者.
首先,我们定义一个处理请求的接口 Handler 以及请求入参 Request :
public interface Handler {
void handleRequest(Request request);
}
public class Request {
private String type;
// 省略getter、setter
}
然后,我们创建3个具体的处理者类实现这个接口,在具体处理者类的实现中,首先判断自己是否能够处理请求,如果能够处理,则进行处理;否则将请求传递给下一个处理者。代码如下:
public class ConcreteHandlerA implements Handler {
private Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public void handleRequest(Request request) {
if (request.getType().equals("A")) {
// 处理请求的逻辑
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
public class ConcreteHandlerB implements Handler {
private Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public void handleRequest(Request request) {
if (request.getType().equals("B")) {
// 处理请求的逻辑
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
public class ConcreteHandlerC implements Handler {
private Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public void handleRequest(Request request) {
if (request.getType().equals("C")) {
// 处理请求的逻辑
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
接下来,我们创建一个客户端类 Client,用于创建处理者对象并组成责任链的结构:
public class Client {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
Handler handlerC = new ConcreteHandlerC();
handlerA.setSuccessor(handlerB);
handlerB.setSuccessor(handlerC);
// 创建请求并发送给第一个处理者
Request request = new Request("A");
handlerA.handleRequest(request);
}
}
在客户端类中,我们创建了具体的处理者对象,并通过 setSuccessor() 方法将它们组成一个责任链的结构。然后,创建一个请求对象,并将请求发送给第一个处理者.
基于接口的实现方式简单直观,每个处理者只需要实现一个接口即可。但是它的缺点是如果责任链较长,需要创建多个处理者对象,增加了系统的复杂性和资源消耗。下面基于 Spring 框架实现一个高级版的责任链模式.
在实际开发中,一个请求会在多个处理器之间流转,每个处理器都可以处理请求.
假设我们有一个 Spring 框架开发的订单处理系统,订单需要依次经过订单检查、库存处理、支付处理。如果某个处理环节无法处理订单,将会终止处理并返回错误信息,只有每个处理器都完成了请求处理,这个订单才算法下单成功.
首先,我们定义一个订单类 Order :
@Data
@AllArgsConstructor
public class orderNo {
private String orderNumber;
private String paymentMethod;
private boolean stockAvailability;
private String shippingAddress;
}
然后,我们定义一个抽象订单处理者类 OrderHandler :
public abstract class OrderHandler {
public abstract void handleOrder(Order order);
}
接下来,我们创建具体的订单处理者类继承自抽象订单处理者类,实现相应的方法,并注册到 Spring 中, 。
@Component
public class CheckOrderHandler extends OrderHandler {
public void handleOrder(Order order) {
if (StringUtils.isBlank(order.getOrderNo())) {
throw new RuntimeException("订单编号不能为空");
}
if (order.getPrice().compareTo(BigDecimal.ONE) <= 0) {
throw new RuntimeException("订单金额不能小于等于0");
}
if (StringUtils.isBlank(order.getShippingAddress())) {
throw new RuntimeException("收货地址不能为空");
}
System.out.println("订单参数检验通过");
}
}
@Component
public class StockHandler extends OrderHandler {
public void handleOrder(Order order) {
if (!order.isStockAvailability()) {
throw new RuntimeException("订单库存不足");
}
System.out.println("库存扣减成功");
}
}
@Component
public class AliPaymentHandler extends OrderHandler {
public void handleOrder(Order order) {
if (!order.getPaymentMethod().equals("支付宝")) {
throw new RuntimeException("不支持支付宝以外的支付方式");
}
System.out.println("支付宝预下单成功");
}
}
在具体订单处理者类的实现中, CheckOrderHandler 负责做订单参数检查、 StockHandler 负责做库存扣减、 AliPaymentHandler 负责做预下单,每个处理者的逻辑都是相互独立各不不干扰.
最后,我们创建一个订单生产链条 BuildOrderChain ,用于组成责任链的链条处理结构:
@Component
public class BuildOrderChain {
@Autowired
private AliPaymentHandler aliPaymentHandler;
@Autowired
private CheckOrderHandler checkOrderHandler;
@Autowired
private StockHandler stockHandler;
List<OrderHandler> list = new ArrayList<>();
@PostConstruct
public void init() {
// 1. 检查订单参数
list.add(checkOrderHandler);
// 2. 扣减库存
list.add(stockHandler);
// 3. 支付宝预下单
list.add(aliPaymentHandler);
}
public void doFilter(Order order) {
for (OrderHandler orderHandler : this.list) {
orderHandler.handleOrder(order);
}
}
}
订单生产链条 BuildOrderChain 类中,我们通过 @PostConstruct 注解下的 init() 初始化方法,将具体的订单处理者按代码顺序组成一个责任链的结构。然后通过 doFilter(order) 方法遍历处理者集合依次处理.
运行代码:
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class OrderChainTest {
@Autowired
private BuildOrderChain buildOrderChain;
@Test
public void test() {
Order order = new Order("123456", "支付宝",
true, "长沙", new BigDecimal("100"));
buildOrderChain.doFilter(order);
}
}
-------------------------------
订单参数检验通过
库存扣减成功
支付宝预下单成功
可以看到订单依次经过校验处理器、库存处理器和支付处理器进行处理,直到最后完成整个订单的处理.
在举个例子,假如我们的订单针对的是虚拟不限库存商品,我们不需要进行库存扣减,那我们可以直接新建 VirtualGoodsOrderChain 虚拟商品订单生产链条类,代码如下, 。
@Component
public class VirtualGoodsOrderChain {
@Autowired
private AliPaymentHandler aliPaymentHandler;
@Autowired
private CheckOrderHandler checkOrderHandler;
List<OrderHandler> list = new ArrayList<>();
@PostConstruct
public void init() {
// 1. 检查订单参数
list.add(checkOrderHandler);
// 2 支付宝预下单
list.add(aliPaymentHandler);
}
public void doFilter(Order order) {
for (OrderHandler orderHandler : this.list) {
orderHandler.handleOrder(order);
}
}
}
运行代码:
@Test
public void virtualOrderTest() {
Order order = new Order("123456", "支付宝", true, "长沙", new BigDecimal("100"));
virtualGoodsOrderChain.doFilter(order);
}
-------------------------------------------
订单参数检验通过
支付宝预下单成功
总的来说,责任链模式适用于存在多个处理步骤、每个处理步骤具有独立逻辑或条件、需要灵活组合和扩展的场景。通过责任链模式,可以将复杂的处理逻辑拆分为多个独立的处理步骤,并且可以动态地组合和调整处理步骤的顺序,从而提高系统的灵活性和可维护性。希望本文能够帮助读者理解和应用责任链模式,提升软件设计和开发的能力.
模板方法模式是一种行为型设计模式,它定义一个操作(模板方法)的基本组合与控制流程,将一些步骤(抽象方法)推迟到子类中,在使用时调用不同的子类,就可以达到不改变一个操作的基本流程情况下,即可修改其中的某些特定步骤。这种设计方式将特定步骤的具体实现与操作流程分离开来,实现了代码的复用和扩展,从而提高代码质量和可维护性.
模板方法模式包含以下:
模板方法模式的缺点:
Spring
中的 JdbcTemplate、RestTemplate、RabbitTemplate、KafkaTemplate
等。 如上,我们用一个简单的发送短信代码来做模板方法模式的示例:
定义一个发送短信模板 。
/**
* 发送短信模板
*/
public abstract class SmsTemplate {
/**
* 发送方法
*
* @param mobile 手机号
*/
public void send(String mobile) throws Exception {
System.out.println("检查用户一分钟内是否发送过短信,
mobile:" + mobile);
if (checkUserReceiveInOneMinute(mobile)) {
throw new Exception("请等待1分钟后重试");
}
String code = genCode();
if (manufacturer(mobile, code)) {
System.out.println("短信厂商发送短信成功,
mobile:" + mobile + ",code=" + code);
save2redis(mobile, code);
}
}
/**
* 模板方法,由不同的厂商来实现发送短信到手机上
* @return
*/
abstract boolean manufacturer(String mobile, String code);
/**
* 检查1分钟内该手机号是否接收过验证码,1分钟内接收过就不能在发送验证码
* @param mobile
* @return
*/
public boolean checkUserReceiveInOneMinute(String mobile) {
return ...;
}
/**
* 生成6位验证码
* @return
*/
public String genCode() {
return "123456";
}
/**
* 将手机号+验证码存进redis中,给登录接口做校验用
* @param mobile
* @param code
*/
public void save2redis(String mobile, String code) {
...
}
}
添加两个不同厂商实现的子类 。
/**
* 阿里云短信发送
*/
public class AliyunSmsSend extends SmsTemplate{
@Override
boolean manufacturer(String mobile, String code) {
System.out.println("读取阿里云短信配置");
System.out.println("创建阿里云发送短信客户端");
System.out.println("阿里云发送短信成功");
return true;
}
}
/**
* 腾讯云短信发送
*/
public class TencentSmsSend extends SmsTemplate {
@Override
boolean manufacturer(String mobile, String code) {
System.out.println("读取腾讯云短信配置");
System.out.println("创建腾讯云发送短信客户端");
System.out.println("腾讯云发送短信成功");
return true;
}
}
在 Java 程序中进行调用 。
public class Main {
public static void main(String[] args) throws Exception {
SmsTemplate smsTemplate1 = new AliyunSmsSend();
smsTemplate1.send("13333333333");
System.out.println("---------------------------");
SmsTemplate smsTemplate2 = new TencentSmsSend();
smsTemplate2.send("13333333333");
}
}
输出如下:
检查用户一分钟内是否发送过短信,mobile:13333333333
读取阿里云短信配置
创建阿里云发送短信客户端
阿里云发送短信成功
短信厂商发送短信成功,mobile:13333333333,code=123456
---------------------------
检查用户一分钟内是否发送过短信,mobile:13333333333
读取腾讯云短信配置
创建腾讯云发送短信客户端
腾讯云发送短信成功
短信厂商发送短信成功,mobile:13333333333,code=123456
我们来看看模板方法模式的组成:
SmsTemplate
中定义了发送短信的基本流程操作
AliyunSmsSend、TencentSmsSend
继承抽象类,实现抽象方法 manufacturer(String mobile, String code)
,定义流程中的可变部分。 send(mobile)
,在模板方法中完成了基本流程组合与条件控制。 在 Spring 中实现模板方法模式,是非常简单的,我们只需要对上述的 Java 代码示例的 AliyunSmsSend 类稍作改造,加上 @Component 注解就行, 。
/**
* 阿里云短信发送
*/
@Component
public class AliyunSmsSend extends SmsTemplate{
@Override
boolean manufacturer(String mobile, String code) {
IUserService userService = SpringUtil.getBean(IUserService.class);
System.out.println("读取阿里云短信配置");
System.out.println("创建阿里云发送短信客户端");
System.out.println("阿里云发送短信成功");
return true;
}
}
如果在 AliyunSmsSend 类中需要注入其他 bean ,通过 cn.hutool.extra.spring.SpringUtil.getBean(...) 方法获取对应 bean 就行.
在Java8 中,还可以使用函数表达式来替换抽象方法,代码如下, 。
/**
* 发送短信模板
*/
public class SmsTemplateLambda {
/**
* 发送短信
* @param mobile 手机号
* @param biFunction
* @throws Exception
*/
public void send(String mobile,
BiFunction<String, String, Boolean> biFunction) throws Exception {
System.out.println("检查用户一分钟内是否发送过短信,mobile:" + mobile);
if (checkUserReceiveInOneMinute(mobile)) {
throw new Exception("请等待1分钟后重试");
}
String code = genCode();
if (biFunction.apply(mobile, code)) {
System.out.println("短信厂商发送短信成功,mobile:"
+ mobile + ",code=" + code);
save2redis(mobile, code);
}
}
...
}
通过 BiFunction 函数,将不同厂商发送短信到用户手机的代码在 send(mobile) 方法中分离处理.
调用方法如下:
public static void main(String[] args) throws Exception {
SmsTemplateLambda smsTemplateLambda = new SmsTemplateLambda();
smsTemplateLambda.send("1333333333", (s, s2) -> {
System.out.println("读取阿里云短信配置");
System.out.println("创建阿里云发送短信客户端");
System.out.println("阿里云发送短信成功");
return true;
});
smsTemplateLambda.send("1333333333", (s, s2) -> {
System.out.println("读取腾讯云短信配置");
System.out.println("创建腾讯云发送短信客户端");
System.out.println("腾讯云发送短信成功");
return true;
});
}
可以看到,我们可以只在调用 SmsTemplateLambda 类的 send(mobile) 方法时,才实现不同厂商发送短信到手机的具体逻辑。好处就是每增加一个模板方法时,不用增加具体的子类实现,减少类的创建与降低子类的实现成本.
模板方法模式通过定义一个流程基本操作也就是模板方法,将具体的实现步骤推迟到子类中,使得子类可以灵活地实现可变的行为,这是模板方法模式的核心思想与价值所在.
订阅发布模式(Publish-Subscribe Pattern)是一种行之有效的解耦框架与业务逻辑的方式,也是一种常见的观察者设计模式,它被广泛应用于事件驱动架构中.
观察者模式的各角色定义如下.
优点:
缺点
interface Subscriber {
void update(String message);
}
class Publisher {
private Map<String, List<Subscriber>> subscribers = new HashMap<>();
public void subscribe(String topic, Subscriber subscriber) {
List<Subscriber> subscriberList = subscribers.get(topic);
if (subscriberList == null) {
subscriberList = new ArrayList<>();
subscribers.put(topic, subscriberList);
}
subscriberList.add(subscriber);
}
public void unsubscribe(String topic, Subscriber subscriber) {
List<Subscriber> subscriberList = subscribers.get(topic);
if (subscriberList != null) {
subscriberList.remove(subscriber);
}
}
public void publish(String topic, String message) {
List<Subscriber> subscriberList = subscribers.get(topic);
if (subscriberList != null) {
for (Subscriber subscriber : subscriberList) {
subscriber.update(message);
}
}
}
}
class EmailSubscriber implements Subscriber {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
public void update(String message) {
System.out.println("Send email to " + email + ": " + message);
}
}
class SMSSubscriber implements Subscriber {
private String phoneNumber;
public SMSSubscriber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void update(String message) {
System.out.println("Send SMS to " + phoneNumber + ": " + message);
}
}
public class Main {
public static void main(String[] args) {
Publisher publisher = new Publisher();
Subscriber emailSubscriber1 = new EmailSubscriber("foo@example.com");
Subscriber smsSubscriber1 = new SMSSubscriber("1234567890");
publisher.subscribe("news", emailSubscriber1);
publisher.subscribe("news", smsSubscriber1);
publisher.publish("news", "发布新消息1");
publisher.unsubscribe("news", smsSubscriber1);
publisher.publish("news", "发布新消息2");
}
}
打印输出如下:
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 。
public class OrderEvent extends ApplicationEvent {
void onApplicationEvent(Object event) {
...
}
}
@Component
public class OrderListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
// 生成订单、删除购物车、扣减库存
...
}
}
@Resource
private ApplicationEventPublisher applicationEventPublisher;
private void saveOrder(MallUserVO mallUserVO, Long couponUserId, List<ShopCatVO> shopcatVOList, String orderNo) {
// 订单检查
...
// 生成订单号
String orderNo = NumberUtil.genOrderNo();
// 发布订单事件,在事件监听中处理下单逻辑
applicationEventPublisher.publishEvent(new OrderEvent(orderNo, mallUserVO, couponUserId, shopcatVOList));
// 所有操作成功后,将订单号返回
return orderNo;
...
}
通过事件监听机制,我们将下单逻辑拆分成如下步骤:
每个步骤都是各自独立 。
如上的代码已经实现了订阅发布模式,成功解耦了下单逻辑。建议大家在日常开发中多加思考哪些业务流程可以适用,例如微服务项目中订单支付成功后需要通知用户、商品、活动等多个服务时,可以考虑使用订阅发布模式。解耦发布者和订阅者,发布者只管发布消息,不需要知道有哪些订阅者,也不需要知道订阅者的具体实现。订阅者只需要关注自己感兴趣的消息即可。这种松耦合的设计使得系统更容易扩展和维护.
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端(即算法的调用者).
策略模式的各角色定义如下.
优点:
缺点:
策略模式适用于以下场景:
假设我们有一个计算器程序,它可以根据用户输入的不同运算符(+、-、*、/)来执行不同的算术运算。我们可以使用策略模式来实现这个功能,具体步骤如下:
我们首先定义一个策略接口 Strategy ,它声明了一个 doOperation 方法,用于执行具体的运算.
// 策略接口
public interface Strategy {
// 执行运算
public int doOperation(int num1, int num2);
}
然后我们实现四个具体的策略类,分别是 AddStrategy 、SubtractStrategy 、MultiplyStrategy 和 DivideStrategy ,它们都实现了 Strategy 接口,并重写了 doOperation 方法.
// 加法策略
public class AddStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// 减法策略
public class SubtractStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// 乘法策略
public class MultiplyStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
// 除法策略
public class DivideStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
if (num2 == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return num1 / num2;
}
}
接下来我们定义一个上下文类 Context ,它持有一个 Strategy 的引用,并提供了一个构造方法和一个 executeStrategy 方法。构造方法用于传入具体的策略对象,executeStrategy 方法用于调用策略对象的 doOperation 方法.
// 上下文类
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
最后我们编写一个测试类,用于创建不同的策略对象和上下文对象,并根据用户的输入来执行不同的算法.
// 测试类
public class StrategyTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数:");
int num1 = scanner.nextInt();
System.out.println("请输入运算符(+、-、*、/):");
String operator = scanner.next();
System.out.println("请输入第二个数:");
int num2 = scanner.nextInt();
scanner.close();
// 根据运算符创建不同的策略对象
Strategy strategy;
switch (operator) {
case "+" -> strategy = new AddStrategy();
case "-" -> strategy = new SubtractStrategy();
case "*" -> strategy = new MultiplyStrategy();
case "/" -> strategy = new DivideStrategy();
default -> {
System.out.println("无效的运算符");
return;
}
}
// 创建上下文对象,并传入策略对象
Context context = new Context(strategy);
// 调用上下文对象的方法,执行策略对象的算法
int result = context.executeStrategy(num1, num2);
// 输出结果
System.out.println(num1 + " " + operator + " " + num2 + " = " + result);
}
}
请输入第一个数:
1
请输入运算符(+、-、*、/):
+
请输入第二个数:
1
1 + 1 = 2
在 Spring 框架中,也有很多地方使用了策略模式,比如 BeanFactory 的实现类,它们都实现了一个 BeanFactory 接口,但是具体的实例化和管理 Bean 的方式不同。比如 XmlBeanFactory 是从 XML 文件中读取 Bean 的定义,而 AnnotationConfigApplicationContext 是从注解中读取 Bean 的定义.
我们可以使用 Spring 的依赖注入功能,来实现策略模式,具体步骤如下:
我们还是使用上面的计算器程序作为例子,首先定义一个策略接口 Strategy ,它声明了一个 doOperation 方法,用于执行具体的运算.
// 策略接口
public interface Strategy {
// 执行运算
public int doOperation(int num1, int num2);
}
然后我们实现四个具体的策略类,分别是 AddStrategy 、SubtractStrategy 、MultiplyStrategy 和 DivideStrategy ,它们都实现了 Strategy 接口,并重写了 doOperation 方法。同时,我们给每个策略类添加一个 @Component 注解,表示它们是 Spring 容器管理的组件.
// 加法策略
@Component
public class AddStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// 减法策略
@Component
public class SubtractStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// 乘法策略
@Component
public class MultiplyStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
// 除法策略
@Component
public class DivideStrategy implements Strategy {
@Override
public int doOperation(int num1, int num2) {
if (num2 == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return num1;
}
}
好的,我继续写.
接下来我们定义一个上下文类 Context ,它持有一个 Strategy 的引用,并提供了一个构造方法和一个 executeStrategy 方法。构造方法用于传入具体的策略对象,executeStrategy 方法用于调用策略对象的 doOperation 方法。同时,我们给上下文类添加一个 @Component 注解,表示它也是 Spring 容器管理的组件.
// 上下文类
@Component
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
为了让 Spring 容器能够自动注入不同的策略对象,我们需要使用 @Autowired 注解来标注 Context 类的构造方法,并使用 @Qualifier 注解来指定具体的策略类。这样,我们就可以根据不同的运算符来创建不同的上下文对象,而不需要手动创建策略对象.
// 上下文类
@Component
public class Context {
@Autowired
private List<Strategy> list;
public Strategy getBean(Class tClass) throws Exception {
for (Strategy strategy : list) {
if (strategy.getClass() == tClass) {
return strategy;
}
}
throw new Exception("获取策略失败");
}
}
最后我们编写一个测试类,用于从 Spring 容器中获取 Context 对象,并根据用户的输入来执行不同的算法.
// 测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class StrategyTest {
// 从Spring容器中获取Context对象
@Autowired
private Context context;
@Test
public void test() throws Exception {
System.out.println("请输入第一个数:2");
int num1 = 2;
System.out.println("请输入运算符(+、-、*、/):*");
String operator = "*";
System.out.println("请输入第二个数:3");
int num2 = 3;
// 根据运算符创建不同的上下文对象
Strategy strategy;
switch (operator) {
case "+" -> strategy = context.getBean(AddStrategy.class);
case "-" -> strategy = context.getBean(SubtractStrategy.class);
case "*" -> strategy = context.getBean(MultiplyStrategy.class);
case "/" -> strategy = context.getBean(DivideStrategy.class);
default -> {
System.out.println("无效的运算符");
return;
}
}
// 调用上下文对象的方法,执行策略对象的算法
int result = strategy.doOperation(num1, num2);
// 输出结果
System.out.println(num1 + " " + operator + " " + num2 + " = " + result);
}
}
请输入第一个数:2
请输入运算符(+、-、*、/):*
请输入第二个数:3
2 * 3 = 6
总的来说策略模式是一种常用的行为型设计模式,它可以将不同的算法封装在不同的类中,并让它们可以相互替换。策略模式可以避免使用多重条件语句,提高代码的可读性和可维护性,同时也可以实现开闭原则,增加系统的灵活性。但是策略模式也会增加客户端和系统的复杂度,因此需要根据具体的情况来权衡利弊.
至此本文所讲的四种常用设计模式就全部介绍完了,希望能对大家有所帮助.
关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,你的关注将是我的更新动力! 。
最后此篇关于万字详解常用设计模式的文章就讲到这里了,如果你想了解更多关于万字详解常用设计模式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
关闭。这个问题需要更多focused .它目前不接受答案。 想改善这个问题吗?更新问题,使其仅关注一个问题 editing this post . 4年前关闭。 Improve this questi
.NET 框架:4.5.1 我在 Blend for visual studio 2015 中遇到一个奇怪的错误,我找不到它的来源。 如果我在 VS 中打开我的 WPF 解决方案,它会加载并运行良好。
我经常遇到这样的问题,与 Hierarchical RESTful URL design 非常相似 假设该服务仅提供用户上传文档。 POST, GET /accounts PUT, DELETE /a
在 Rails 应用程序中,我使用 devise 来管理我的用户,而我用来销毁 session 的链接不再有效。它正在工作,现在我添加了事件管理员,但没有。 我的链接是 :delete, :clas
我已经坚持了超过 24 小时,试图按照此处发布的其他解决方案进行操作,但我无法使其正常工作。我是 Rails 新手,需要帮助! 我想让我的/users/edit 页面正常工作,以便我可以简单地更改用户
Devise 在以下情况下不会使用户超时: 用户登录,关闭选项卡,然后在超时 + X 分钟内重新访问该 URL。用户仍处于登录状态。 如果选项卡已打开并且稍后刷新/单击,则超时可以正常工作。这意味着
我想使用这样的 slider 我希望该 slider 根据提供给它的值进行相应调整。到目前为止,我只能应用具有渐变效果的背景,但无法获得这种效果。请通过提供样式代码来帮助我。
您应该为每种方法创建一个请求/响应对象,还是应该为每个服务创建一个? 如果我在所有方法中使用它,我的服务请求对象中将只有 5 个不同的东西,因为我对几乎所有方法使用相同的输入。 响应对象将只有一个字典
我正在尝试在 REST 中对实体的附件进行建模。假设一个缺陷实体可以附加多个附件。每个附件都有描述和一些其他属性(上次修改时间、文件大小...)。附件本身是任何格式的文件(jpeg、doc ...)
我有以下表格: Blogs { BlogName } BlogPosts { BlogName, PostTitle } 博客文章同时建模一个实体和一个关系,根据 6nf(根据第三个宣言)这是无效的。
如果 A 类与 B、C 和 D 类中的每一个都有唯一的交互,那么交互的代码应该在 A 中还是在 B、C 和 D 中? 我正在编写一个小游戏,其中许多对象可以与其他对象进行独特的交互。例如,EMP点击
关于如何记住我与 Omniauth 一起工作似乎有些困惑。 根据这个wiki ,您需要在 OmniauthCallbacksController 中包含以下内容: remember_me(user)
设计问题: 使用 非线程安全 组件(集合,API,...)在/带有 多线程成分 ... 例子 : 组件 1 :多线程套接字服务器谁向消息处理程序发送消息... 组件 2 :非线程安全 消息处理程序 谁
我们目前正在设计一个 RESTful 应用程序。我们决定使用 XML 作为我们的基本表示。 我有以下关于在 XML 中设计/建模应用程序数据的问题。 在 XML 中进行数据建模的方法有哪些?从头开始然
我正在设计一个新的 XSD 来从业务合作伙伴那里获取积分信息。对于每笔交易,合作伙伴必须提供至少一种积分类型的积分值。我有以下几点:
设计支持多个版本的 API 的最佳方法是什么。我如何确保即使我的数据架构发生更改(微小更改),我的 api 的使用者也不会受到影响?任何引用架构、指南都非常有用。 最佳答案 Mark Nottingh
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 4 年前。 Improv
我想用 php 创建一个网站,其工作方式与 https://www.bitcoins.lc/ 相同。确实,就每个页面上具有相同布局但内容会随着您更改链接/页面而改变而言,我如何在 php 中使用lay
我有一个关于编写 Swing UI 的问题。如果我想制作一个带有某些选项的软件,例如在第一个框架上,我有三个按钮(新建、选项、退出)。 现在,如果用户单击新按钮,我想将框架中的整个内容更改为其他内容。
我正在尝试找出并学习将应用程序拥有的一堆Docker容器移至Kubernetes的模式和最佳实践。诸如Pod设计,服务,部署之类的东西。例如,我可以创建一个其中包含单个Web和应用程序容器的Pod,但
我是一名优秀的程序员,十分优秀!