策略模式:定義一組算法,將每個算法封裝起來,使它們可以互相替換,且算法的變換不會影響使用算法的客户。
• 抽象策略(Strategy)類:這是一個抽象角色,通常由一個接口或抽象類實現。此角色給出所有的具體策略類所需的接口。
• 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現或行為。
• 環境(Context)類:持有一個策略類的引用,最終給客户端調用。
現在有三種促銷策略:
//抽象策略(Strategy)類
public interface Strategy {
void show();
}
//具體策略(Concrete Strategy)類
public class StrategyA implements Strategy {
public void show() { System.out.println("買一送一"); }
}
public class StrategyB implements Strategy {
public void show() { System.out.println("滿200減50"); }
}
public class StrategyC implements Strategy {
public void show() { System.out.println("加1元換購"); }
}
//定義環境角色(Context):用於連接上下文,即把促銷活動推銷給客户,這裏可以理解為銷售員
public class SalesMan {
//持有抽象策略角色的引用
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
//向客户展示促銷活動
public void salesManShow(){
strategy.show();
}
}
業務方使用策略時,傳統寫法是這樣
public class Client {
public static void main(String[] args) {
String festival = "Spring"; // 春節
SalesMan man = null;
if ("Spring".equals(festival)) {
man = new SalesMan(new StrategyA());
} else if ("MidAutumn".equals(festival)) {
man = new SalesMan(new StrategyB());
} else if ("Christmas".equals(festival)) {
man = new SalesMan(new StrategyC());
}
man.salesManShow();
}
}
問題:
上層業務(Client)必須寫一堆 if-else 來選擇策略。
當節日越來越多,這部分就會變得臃腫、難維護。
定義一個“策略工廠”,專門負責根據條件創建策略對象
public class StrategyFactory {
public static Strategy getStrategy(String festival) {
if ("Spring".equals(festival)) {
return new StrategyA();
} else if ("MidAutumn".equals(festival)) {
return new StrategyB();
} else if ("Christmas".equals(festival)) {
return new StrategyC();
}
throw new IllegalArgumentException("未知節日:" + festival);
}
}
public class Client {
public static void main(String[] args) {
String festival = "MidAutumn"; // 中秋節
// ✅ 不需要自己寫 if-else 了,交給工廠選擇策略
Strategy strategy = StrategyFactory.getStrategy(festival);
// ✅ 策略交給 SalesMan 執行
SalesMan man = new SalesMan(strategy);
man.salesManShow();
}
}
「結合 if-else + 工廠模式,把具體的實現類對象交給策略模式,讓上游業務調用方解放策略的選擇。」
逐字拆解理解:
|
關鍵詞
|
含義
|
|
if-else + 工廠模式 |
工廠內部仍然用 |
|
把具體的實現類對象交給策略模式 |
工廠負責創建正確的策略( |
|
讓上游業務調用方解放策略的選擇 |
調用方只需告訴“是什麼場景”(如節日名稱),不需要自己 |
把“如何選擇策略”的邏輯(if-else)封裝進“工廠模式”,
把“如何執行策略”的邏輯封裝進“策略模式”,
從而讓“上游業務調用方”只需傳一個簡單參數(如節日名),就能自動使用正確的策略。
為了更優雅,我們甚至可以去掉工廠中的 if-else,用 Map 註冊策略。下面舉一個生產中二點實例:用註冊工廠模式 + 策略模式,解決登錄邏輯中 if-else 冗餘、擴展性差的問題。
UserService.login()
@Service
public class UserService {
public LoginResp login(LoginReq loginReq){
if ("account".equals(loginReq.getType())) {
// 用户名密碼登錄邏輯
} else if ("sms".equals(loginReq.getType())) {
// 手機驗證碼登錄邏輯
} else if ("we_chat".equals(loginReq.getType())) {
// 微信登錄邏輯
}
}
}
❌ 主要問題:不符合開閉原則 ,比如:新增 QQ 登錄、支付寶登錄等要改源碼
用 策略模式 封裝各種登錄方式的差異,
用 工廠方法模式 管理和分配不同的策略,
讓UserService專注調度,不再關心具體實現。
✅ 策略模式負責:
定義一系列「登錄方式」算法,讓它們可以互相替換,而不影響使用方。
抽象策略類:
public interface UserGranter {
LoginResp login(LoginReq loginReq);
}
具體策略實現:
@Component
public class AccountGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
System.out.println("賬號密碼登錄");
return new LoginResp();
}
}
@Component
public class SmsGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
System.out.println("短信驗證碼登錄");
return new LoginResp();
}
}
@Component
public class WeChatGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
System.out.println("微信登錄");
return new LoginResp();
}
}
✅ 註冊工廠方法模式負責:
把策略的創建、管理邏輯統一封裝起來,解耦外部調用。
@Component
public class UserLoginFactory implements ApplicationContextAware {
private static final Map<String, UserGranter> granterPool = new ConcurrentHashMap<>();
@Autowired
private LoginTypeConfig loginTypeConfig;
// 讀取配置文件信息,並注入Bean
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
loginTypeConfig.getTypes().forEach((k, v) -> {
granterPool.put(k, (UserGranter) applicationContext.getBean(v));
});
}
// 根據登錄類型返回對應策略
public UserGranter getGranter(String grantType) {
return granterPool.get(grantType);
}
}
✅ 配置化管理(靈活擴展)
application.yml
login:
types:
account: accountGranter
sms: smsGranter
we_chat: weChatGranter
LoginTypeConfig.java
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "login")
public class LoginTypeConfig {
private Map<String, String> types;
}
✅ 改造後的 Service
@Service
public class UserService {
@Autowired
private UserLoginFactory factory;
public LoginResp login(LoginReq loginReq) {
UserGranter granter = factory.getGranter(loginReq.getType());
if (granter == null) {
LoginResp resp = new LoginResp();
resp.setSuccess(false);
return resp;
}
return granter.login(loginReq);
}
}
|
對比項
|
改造前(if-else)
|
改造後(工廠+策略)
|
|
可讀性
|
❌ 複雜、混亂
|
✅ 清晰、結構化
|
|
擴展性
|
❌ 改源碼才能新增登錄方式
|
✅ 新增類 + 配置即可
|
|
可維護性
|
❌ 所有邏輯堆在一個方法
|
✅ 各登錄方式獨立實現
|
|
複用性
|
❌ 難以單獨測試某種登錄
|
✅ 可獨立注入並複用
|
|
設計原則
|
❌ 違反開閉原則
|
✅ 完全符合開閉原則
|
|
依賴關係
|
緊耦合
|
鬆耦合
|
只需要三步 ✅:
① 新增策略類
@Component
public class QQGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
System.out.println("QQ 登錄");
return new LoginResp();
}
}
② 修改配置文件
login:
types:
account: accountGranter
sms: smsGranter
we_chat: weChatGranter
qq: qQGranter
③ 不改任何 Java 代碼,系統自動支持 QQ 登錄
七、應用
- 訂單的支付策略
• 支付寶支付
• 微信支付
• 銀行卡支付
• 現金支付 - 解析不同類型excel
• xls格式
• xlsx格式 - 打折促銷
• 滿300元9折
• 滿500元8折
• 滿1000元7折 - 物流運費階梯計算
• 5kg以下
• 5kg-10kg
• 10kg-20kg
• 20kg以上
一句話總結:只要代碼中有冗長的 if-else 或 switch 分支判斷都可以採用策略模式優化。
策略模式封裝“行為的變化”,工廠模式封裝“對象的創建”,
兩者結合可以讓Service業務層完全不用管“選哪個策略、怎麼 new 策略”,
只需告訴系統“我要處理哪個場景”,系統自動選擇並執行正確的行為。