一文搞懂設(shè)計模式—策略模式
在軟件開發(fā)中,經(jīng)常會遇到需要根據(jù)不同的條件來實現(xiàn)不同行為的場景。這種場景下,策略模式(Strategy Pattern)就是一種非常有用的設(shè)計模式。
策略模式屬于行為型模式,允許我們定義一系列算法,并將其封裝在獨立的策略類中,使得它們可以互相替換。通過使用策略模式,我們能夠靈活地選擇和切換不同的算法,而無需修改原有的代碼,替代?量 if else 的邏輯。
使用場景
策略模式通常在以下情況下被使用:
- 當(dāng)存在多種實現(xiàn)方式,且需要在運行時動態(tài)選擇具體實現(xiàn)時,策略模式非常有用。例如,一個購物應(yīng)用可能需要根據(jù)用戶的會員等級來計算折扣,不同等級對應(yīng)不同的計算方式,這時就可以使用策略模式來實現(xiàn)。
- 當(dāng)存在一組類似的行為,只是實現(xiàn)細(xì)節(jié)略有不同,但又不希望通過繼承來添加新的子類時,策略模式也很適用。它將這組行為封裝在獨立的策略類中,并通過委托的方式在上下文對象中使用。
例如:
- 支付方式選擇:一個電子商務(wù)平臺可以根據(jù)用戶的選擇來使用不同的支付策略,例如信用卡支付、支付寶支付、微信支付等。
- 排序算法選擇:一個排序工具可以根據(jù)用戶的需求選擇不同的排序算法,例如快速排序、歸并排序等。
- 數(shù)據(jù)驗證:一個表單驗證工具可以根據(jù)不同的驗證規(guī)則采用不同的驗證策略,例如長度驗證、格式驗證等。
這些只是策略模式的一些例子,實際應(yīng)用場景非常豐富。通過使用策略模式,我們可以將算法或行為與具體的業(yè)務(wù)邏輯解耦,使得系統(tǒng)更加靈活和可擴(kuò)展。
策略模式實現(xiàn)
在策略模式中,有三個核心角色:上下文(Context)、策略接口(Strategy)和具體策略類(Concrete Strategy)。
- 上下文(Context):封裝了具體策略的執(zhí)行邏輯,提供給客戶端使用的接口。上下文通常包含一個指向策略接口的引用,用于調(diào)用具體策略的方法。
- 策略接口(Strategy):定義了一組算法或行為的公共接口,所有具體策略都必須實現(xiàn)該接口。
- 具體策略類(Concrete Strategy):實現(xiàn)了策略接口,提供了具體的算法或行為。
下面我們來實現(xiàn)一下策略模式:
步驟 1
創(chuàng)建策略接口。
//策略接口
public interface PaymentStrategy {
void pay(double amount);
}
步驟2
創(chuàng)建策略接口實現(xiàn)類。
//具體策略類
public class CreditCardPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用信用卡支付:" + amount);
// 具體的支付邏輯
}
}
public class WeChatPay implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用微信支付:" + amount);
// 具體的支付邏輯
}
}
注意:在實際項目中,我們一般通過工廠方法模式來實現(xiàn)策略類的聲明。
實現(xiàn)關(guān)系如下:
圖片
步驟 3
創(chuàng)建 Context 類。
// 上下文類
public class PaymentContext {
private PaymentStrategy paymentStrategy;
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void pay(double amount) {
paymentStrategy.pay(amount);
}
}
調(diào)用一下:
// 使用示例
public class Main {
public static void main(String[] args) {
PaymentStrategy strategy = new CreditCardPayment();
PaymentContext context = new PaymentContext(strategy);
context.pay(100.0);
strategy = new WeChatPay();
context = new PaymentContext(strategy);
context.pay(200.0);
}
}
輸出:
使用信用卡支付:100.0
使用微信支付:200.0
在上面的代碼中,我們定義了一個 PaymentStrategy 接口作為策略接口,兩個具體的策略類 CreditCardPayment 和 WeChatPay 實現(xiàn)了該接口。然后,我們創(chuàng)建了一個 PaymentContext 上下文對象,并根據(jù)需要傳入不同的策略實例進(jìn)行支付操作。
策略模式的優(yōu)缺點
策略模式的優(yōu)點包括:
- 松耦合:策略模式將不同的策略封裝在獨立的類中,與上下文對象解耦,增加了代碼的靈活性和可維護(hù)性。
- 易于擴(kuò)展:可以通過添加新的策略類來擴(kuò)展系統(tǒng)的功能,無需修改現(xiàn)有代碼。
- 符合開閉原則:對于新的策略,無需修改上下文對象,只需要實現(xiàn)新的策略接口即可。
策略模式的缺點包括:
- 類數(shù)量增多:每個具體策略都需要一個獨立的類,如果策略較多,將導(dǎo)致類的數(shù)量增加。
- 上層必須知道所有策略類:上層模塊必須知道有哪些策略,并選擇合適的策略進(jìn)行使用,這與迪米特法則是相違背的,我只是想使用了一個策略,我憑什么就要了解這個策略呢?那要你的封裝類還有什么 意義?這是原裝策略模式的一個缺點。
注意事項: 如果一個系統(tǒng)的策略多于四個,就需要考慮使用混合模式,解決策略類膨脹的問題,否則日后的系統(tǒng)維護(hù)就會成為一個燙手山芋。
策略模式優(yōu)化
使用Map取消 Context 類
我們可以將策略實現(xiàn)類放進(jìn) Map 中,根據(jù) key 去選擇具體的策略,就不必事先定義 Context 類。
public static void main(String[] args) {
Map<String, PaymentStrategy> map=new HashMap<>();
map.put("CREDIT_CARD", new CreditCardPayment());
map.put("WECHAT_PAY",new WeChatPay());
map.get("CREDIT_CARD").pay(100.0);
map.get("WECHAT_PAY").pay(200.0);
}
策略枚舉解決策略類膨脹
策略枚舉可以解決策略類過多的問題。
我們對原裝的策略模式進(jìn)行改造,把原有定義在抽象策略中的方法移植到枚舉中,讓枚舉成員成為一個具體策略。
@Slf4j
public enum PaymentStrategyEnum {
CREDIT_CARD {
@Override
public void pay(double amount) {
log.info("使用信用卡支付:" + amount);
// 具體的支付邏輯
}
},
WECHAT_PAY {
@Override
public void pay(double amount) {
log.info("使用微信支付:" + amount);
// 具體的支付邏輯
}
};
public abstract void pay(double amount);
}
在上面的代碼中,我們定義了一個枚舉類型 PaymentStrategy,其中包含兩個枚舉常量 CREDIT_CARD 和 WECHAT_PAY。每個枚舉常量都重寫了 pay() 方法,用于具體的支付邏輯。
// 使用示例
public static void main(String[] args) {
Map<String, PaymentStrategyEnum> map=new HashMap<>();
map.put("CREDIT_CARD", PaymentStrategyEnum.CREDIT_CARD);
map.put("WECHAT_PAY", PaymentStrategyEnum.WECHAT_PAY);
map.get("CREDIT_CARD").pay(100.0);
map.get("WECHAT_PAY").pay(200.0);
}
注意:策略枚舉是一個非常優(yōu)秀和方便的模式,但是它受枚舉類型的限制,每個枚舉項都是 public、final、static 的,擴(kuò)展性受到了一定的約束,因此在系統(tǒng)開發(fā)中,策略枚舉一般擔(dān)當(dāng)不經(jīng)常發(fā)生變化的角色。
SpringBoot中的策略模式
SpringBoot中使用策略模式更加方便:
public interface Test {
void print(String name);
}
@Service("testA")
@Slf4j
public class TestA implements Test{
@Override
public void print(String name) {
log.info("實現(xiàn)類A"+name);
}
}
@Service("testB")
@Slf4j
public class TestB implements Test{
@Override
public void print(String name) {
log.info("實現(xiàn)類B"+name);
}
}
使用的時候 @Autowired 或者 @Resource 即可,SpringBoot會幫我們把實現(xiàn)類自動注入注入Map。
@Resource
private Map<String,Test> map;
Test test = map.get("你想拿出的具體策略類");
test.print("hello world");
總結(jié)
策略模式是一種強大而靈活的設(shè)計模式,它可以幫助我們處理不同的算法或行為,并使系統(tǒng)更具可維護(hù)性和擴(kuò)展性。通過封裝具體的策略類和使用上下文對象,我們可以輕松地選擇和切換不同的策略,而無需修改現(xiàn)有的代碼。