本文是博主在工作中對常用設計模式的使用經驗總結歸納而來分享給大家。
設計模式一共有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("[email protected]");
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 [email protected]: 釋出新訊息1
Send SMS to 1234567890: 釋出新訊息1
Send email to [email protected]: 釋出新訊息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】每週分享技術乾貨、開源專案、實戰經驗、高效開發工具等,你的關注將是我的更新動力!