Java設(shè)計模式之策略模式的使用(Strategy?Pattern)
策略模式(Strategy Pattern)是一種行為型設(shè)計模式,旨在定義一系列算法,將每個算法封裝起來,并使它們可以互相替換,從而使得算法的變化不會影響使用算法的客戶端。策略模式的主要結(jié)構(gòu)包括策略接口、具體策略類和上下文類,通過將算法的選擇與使用分離,實現(xiàn)了代碼的可維護性和靈活性。
1. 策略模式的動機
在軟件開發(fā)中,經(jīng)常遇到需要在運行時動態(tài)選擇一種算法的情況。例如,排序算法、支付方式、文件壓縮等場景都可能需要在不同條件下選擇不同的算法實現(xiàn)。如果在客戶端代碼中硬編碼這些算法的選擇邏輯,會導(dǎo)致代碼難以維護和擴展。策略模式通過將算法的選擇和實現(xiàn)分離,使得算法可以獨立變化,客戶端代碼可以更簡潔和靈活。
2. 策略模式的結(jié)構(gòu)
策略模式包含以下幾部分:
- 策略接口(Strategy Interface):定義所有支持的算法的公共接口。
- 具體策略類(Concrete Strategies):實現(xiàn)策略接口,定義具體的算法。
- 上下文類(Context Class):使用一個具體策略對象來配置,并維護對策略對象的引用。
3. 策略模式的UML類圖
4. 策略模式的實現(xiàn)
以下是一個使用策略模式的Java示例,該示例演示了如何選擇不同的策略來執(zhí)行操作:
4.1 策略接口
// 定義策略接口 public interface Strategy { void execute(); }
4.2 具體策略類
// 具體策略A public class ConcreteStrategyA implements Strategy { @Override public void execute() { System.out.println("執(zhí)行策略A"); } } // 具體策略B public class ConcreteStrategyB implements Strategy { @Override public void execute() { System.out.println("執(zhí)行策略B"); } }
4.3 上下文類
// 上下文類 public class Context { private Strategy strategy; // 設(shè)置策略 public void setStrategy(Strategy strategy) { this.strategy = strategy; } // 執(zhí)行策略 public void executeStrategy() { if (strategy == null) { throw new IllegalStateException("Strategy未設(shè)置"); } strategy.execute(); } }
4.4 客戶端代碼
public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(); // 使用策略A context.setStrategy(new ConcreteStrategyA()); context.executeStrategy(); // 輸出: 執(zhí)行策略A // 使用策略B context.setStrategy(new ConcreteStrategyB()); context.executeStrategy(); // 輸出: 執(zhí)行策略B } }
5. 策略模式的優(yōu)缺點
優(yōu)點
- 算法可以自由切換:可以在不影響客戶端的情況下更改算法。
- 避免多重條件判斷:使用策略模式可以避免過多的if-else或switch-case語句。
- 擴展性好:增加新的策略時只需添加新的策略類即可,不需要修改現(xiàn)有代碼。
缺點
- 客戶端必須知道所有的策略類:客戶端需要了解每個策略類的具體實現(xiàn),這增加了復(fù)雜度。
- 增加對象數(shù)目:如果策略較多,會增加類的數(shù)量,導(dǎo)致系統(tǒng)變得復(fù)雜。
6. 策略模式的應(yīng)用場景
策略模式適用于以下場景:
- 需要在不同情況下使用不同的算法。
- 有許多相關(guān)類僅僅在行為上有所不同。
- 需要避免使用復(fù)雜的條件語句來選擇不同的行為。
7. 策略模式的變體
策略模式可以與其他設(shè)計模式結(jié)合使用,以增強其功能。例如:
- 組合模式(Composite Pattern):可以將策略模式與組合模式結(jié)合,使得策略的選擇更加靈活。
- 工廠模式(Factory Pattern):可以使用工廠模式來創(chuàng)建策略對象,從而實現(xiàn)策略的動態(tài)選擇。
8. 策略模式與其他設(shè)計模式的比較
- 策略模式 vs. 狀態(tài)模式:兩者結(jié)構(gòu)類似,但策略模式的不同策略是彼此獨立的,而狀態(tài)模式的不同狀態(tài)之間存在一定的關(guān)系。
- 策略模式 vs. 命令模式:命令模式用于封裝請求,將請求與執(zhí)行解耦,而策略模式用于封裝算法,將算法與使用算法的代碼解耦。
9. 策略模式的實現(xiàn)細節(jié)與最佳實踐
9.1 延遲初始化策略
在某些情況下,策略的初始化可能比較耗時,可以使用延遲初始化(Lazy Initialization)來提高性能:
public class Context { private Strategy strategy; public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { if (strategy == null) { // 延遲初始化 strategy = new ConcreteStrategyA(); } strategy.execute(); } }
9.2 使用反射動態(tài)加載策略
為了避免頻繁修改代碼,可以通過反射動態(tài)加載策略:
public class Context { private Strategy strategy; public void setStrategy(String strategyClassName) throws Exception { this.strategy = (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance(); } public void executeStrategy() { strategy.execute(); } }
9.3 使用配置文件管理策略
將策略的配置放在配置文件中,便于管理和維護:
# strategy.properties strategy=ConcreteStrategyA
import java.io.InputStream; import java.util.Properties; public class StrategyLoader { public static Strategy loadStrategy() throws Exception { Properties properties = new Properties(); try (InputStream input = StrategyLoader.class.getResourceAsStream("/strategy.properties")) { properties.load(input); } String strategyClassName = properties.getProperty("strategy"); return (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance(); } }
9.4 策略模式與依賴注入
結(jié)合依賴注入框架(如Spring),可以更加靈活地管理策略的實例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Context { private final Strategy strategy; @Autowired public Context(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } }
10. 策略模式的實際應(yīng)用案例
10.1 支付系統(tǒng)中的策略模式
在一個支付系統(tǒng)中,可能有多種支付方式,如信用卡支付、支付寶支付、微信支付等。通過策略模式,可以根據(jù)用戶選擇的支付方式動態(tài)切換支付策略。
支付策略接口
public interface PaymentStrategy { void pay(double amount); }
具體支付策略類
// 信用卡支付策略 public class CreditCardPaymentStrategy implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("使用信用卡支付:" + amount + "元"); } } // 支付寶支付策略 public class AliPayPaymentStrategy implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("使用支付寶支付:" + amount + "元"); } } // 微信支付策略 public class WeChatPaymentStrategy implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("使用微信支付:" + amount + "元"); } }
支付上下文類
public class PaymentContext { private PaymentStrategy paymentStrategy; // 設(shè)置支付策略 public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } // 執(zhí)行支付 public void pay(double amount) { if (paymentStrategy == null) { throw new IllegalStateException("PaymentStrategy未設(shè)置"); } paymentStrategy.pay(amount); } }
支付策略工廠類
為了更加優(yōu)雅地創(chuàng)建支付策略,可以使用工廠模式:
public class PaymentStrategyFactory { public static PaymentStrategy getPaymentStrategy(String type) { switch (type) { case "CreditCard": return new CreditCardPaymentStrategy(); case "AliPay": return new AliPayPaymentStrategy(); case "WeChat": return new WeChatPaymentStrategy(); default: throw new IllegalArgumentException("未知的支付類型: " + type); } } }
客戶端代碼
public class PaymentDemo { public static void main(String[] args) { PaymentContext context = new PaymentContext(); // 從外部獲取支付類型,例如通過用戶輸入或配置文件 String paymentType = "CreditCard"; // 這里可以根據(jù)實際情況更改 // 使用工廠創(chuàng)建支付策略 PaymentStrategy paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType); // 設(shè)置支付策略 context.setPaymentStrategy(paymentStrategy); // 執(zhí)行支付 context.pay(100.0); // 輸出: 使用信用卡支付:100.0元 // 更改支付策略 paymentType = "AliPay"; paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType); context.setPaymentStrategy(paymentStrategy); context.pay(200.0); // 輸出: 使用支付寶支付:200.0元 // 更改支付策略 paymentType = "WeChat"; paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType); context.setPaymentStrategy(paymentStrategy); context.pay(300.0); // 輸出: 使用微信支付:300.0元 } }
優(yōu)化的重點
- 工廠模式:使用工廠模式來創(chuàng)建支付策略對象,使客戶端代碼更簡潔,策略的創(chuàng)建和選擇更靈活。
- 空策略檢查:在上下文類中增加對策略是否為空的檢查,避免未設(shè)置策略時的運行時錯誤。
- 策略類型動態(tài)獲取:通過從外部(如用戶輸入或配置文件)獲取支付類型,示例代碼更加接近實際應(yīng)用場景。
通過策略模式和工廠模式的結(jié)合,可以實現(xiàn)一個靈活、可擴展且易于維護的支付系統(tǒng)。在實際開發(fā)中,進一步結(jié)合依賴注入框架(如Spring)來管理策略對象,可以提升代碼的可測試性和可擴展性。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot如何配置MySQL和Oracl雙數(shù)據(jù)源(Mybatis)
這篇文章主要介紹了SpringBoot如何配置MySQL和Oracl雙數(shù)據(jù)源(Mybatis)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03SpringMVC 數(shù)據(jù)校驗方法(必看篇)
下面小編就為大家?guī)硪黄猄pringMVC 數(shù)據(jù)校驗方法(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06Android開發(fā)中實現(xiàn)用戶注冊和登陸的代碼實例分享
這篇文章主要介紹了Android開發(fā)中實現(xiàn)用戶注冊和登陸的代碼實例分享,只是實現(xiàn)基本功能,界面華麗度就請忽略啦XD 需要的朋友可以參考下2015-12-12mybatis-plus無法通過logback-spring輸出的解決方法
本文主要介紹了mybatis-plus無法通過logback-spring輸出,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11