策略模式:定義一組算法,將每個算法封裝起來,使它們可以互相替換,且算法的變換不會影響使用算法的客户。

• 抽象策略(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 判斷節日類型,但外部業務層不需要管這個判斷邏輯了。

把具體的實現類對象交給策略模式

工廠負責創建正確的策略(StrategyAStrategyBStrategyC),然後交給 SalesMan 使用。

讓上游業務調用方解放策略的選擇

調用方只需告訴“是什麼場景”(如節日名稱),不需要自己 new 策略對象,也不需要判斷邏輯。


把“如何選擇策略”的邏輯(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 登錄


七、應用

  1. 訂單的支付策略
    • 支付寶支付
    • 微信支付
    • 銀行卡支付
    • 現金支付
  2. 解析不同類型excel
    • xls格式
    • xlsx格式
  3. 打折促銷
    • 滿300元9折
    • 滿500元8折
    • 滿1000元7折
  4. 物流運費階梯計算
    • 5kg以下
    • 5kg-10kg
    • 10kg-20kg
    • 20kg以上
    一句話總結:只要代碼中有冗長的 if-else 或 switch 分支判斷都可以採用策略模式優化。

策略模式封裝“行為的變化”,工廠模式封裝“對象的創建”,
兩者結合可以讓Service業務層完全不用管“選哪個策略、怎麼 new 策略”,
只需告訴系統“我要處理哪個場景”,系統自動選擇並執行正確的行為。