點擊上方“程序員蝸牛g”,選擇“設為星標”
跟蝸牛哥一起,每天進步一點點
程序員蝸牛g
大廠程序員一枚 跟蝸牛一起 每天進步一點點
33篇原創內容
公眾號
一、需求分析:當產品説 "我們要支持 10 種登錄方式"
1. 典型登錄場景:代碼裏的 "聯合國"
假設我們要實現一個支持三種登錄方式的系統:
- 用户名密碼登錄: 需要校驗密碼加密、賬號是否鎖定
- 微信掃碼登錄: 需要調用微信開放平台 API,校驗授權碼
- 手機號驗證碼登錄: 需要生成驗證碼、校驗有效期和正確性
傳統寫法是寫一個LoginService,用if-else判斷登錄類型:
public String login(String loginType, Map<String, Object> params) {
if ("password".equals(loginType)) {
// 用户名密碼登錄邏輯
} else if ("wechat".equals(loginType)) {
// 微信登錄邏輯
} else if ("sms".equals(loginType)) {
// 手機號登錄邏輯
} else {
throw new IllegalArgumentException("不支持的登錄類型");
}
}
這種寫法的問題在於:
- 擴展性差: 新增登錄方式要改if-else,違反開閉原則
- 職責混亂: 所有邏輯擠在一個類裏,可讀性差
- 複用困難: 不同登錄方式的公共邏輯(如用户校驗)無法抽取
2. 設計模式選擇:策略模式解耦算法,工廠模式創建實例
策略模式:將每種登錄方式封裝成獨立策略類,實現統一接口,調用者無需關心具體實現 工廠模式:通過工廠類根據登錄類型創建對應的策略實例,避免調用者直接 new 對象
二、Spring Boot 項目搭建:先搭好 "舞台"
1. 創建 Spring Boot 項目
添加 Web 依賴,項目結構如下:
src/main/java/com/example/login
├── config
│ └── StrategyConfig.java // 策略Bean配置
├── controller
│ └── LoginController.java // 登錄控制器
├── factory
│ └── LoginStrategyFactory.java // 登錄策略工廠
├── model
│ └── LoginRequest.java // 登錄請求參數
├── service
│ ├── impl
│ │ ├── PasswordLoginStrategy.java // 用户名密碼策略
│ │ ├── WechatLoginStrategy.java // 微信策略
│ │ └── SmsLoginStrategy.java // 手機號策略
│ └── LoginStrategy.java // 登錄策略接口
└── Application.java
2. 定義統一登錄策略接口
public interface LoginStrategy {
// 登錄類型標識,如"password"、"wechat"
String getLoginType();
// 登錄方法,參數用Map傳遞不同登錄方式的參數
String execute(Map<String, Object> params);
}
三、策略模式實現:每種登錄方式都是 "獨立演員"
1. 用户名密碼登錄策略
@Service
publicclass PasswordLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return"password";
}
@Override
public String execute(Map<String, Object> params) {
String username = (String) params.get("username");
String password = (String) params.get("password");
// 模擬密碼校驗(實際應從數據庫查詢+密碼解密)
if (!"123456".equals(password)) {
thrownew IllegalArgumentException("密碼錯誤");
}
// 模擬用户校驗
checkUserLocked(username);
return"登錄成功(用户名密碼)";
}
private void checkUserLocked(String username) {
// 調用用户服務檢查賬號是否鎖定
System.out.println("檢查用户" + username + "是否鎖定");
}
}
2. 微信掃碼登錄策略
@Service
publicclass WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return"wechat";
}
@Override
public String execute(Map<String, Object> params) {
String authCode = (String) params.get("authCode");
// 模擬調用微信接口獲取用户信息
String openId = callWechatApi(authCode);
// 模擬數據庫查詢用户綁定關係
String userId = getUserIdByOpenId(openId);
if (userId == null) {
thrownew IllegalArgumentException("微信賬號未綁定系統用户");
}
return"登錄成功(微信掃碼)";
}
private String callWechatApi(String authCode) {
// 實際應調用微信開放平台API
System.out.println("調用微信接口,authCode=" + authCode);
return"wechat_open_id_123";
}
}
3. 手機號驗證碼登錄策略
@Service
publicclass SmsLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return"sms";
}
@Override
public String execute(Map<String, Object> params) {
String phone = (String) params.get("phone");
String code = (String) params.get("code");
// 模擬驗證碼校驗(實際應從Redis獲取)
if (!"666888".equals(code)) {
thrownew IllegalArgumentException("驗證碼錯誤");
}
// 模擬用户校驗
checkPhoneRegistered(phone);
return"登錄成功(手機號驗證碼)";
}
private void checkPhoneRegistered(String phone) {
// 檢查手機號是否註冊
System.out.println("檢查手機號" + phone + "是否註冊");
}
}
四、工廠模式實現:讓 "導演" 決定用哪個 "演員"
1. 登錄策略工廠類
@Component
publicclass LoginStrategyFactory {
privatefinal Map<String, LoginStrategy> strategyMap;
// 通過Spring依賴注入獲取所有LoginStrategy實現類
public LoginStrategyFactory(Map<String, LoginStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public LoginStrategy getStrategy(String loginType) {
LoginStrategy strategy = strategyMap.get(loginType);
if (strategy == null) {
thrownew IllegalArgumentException("不支持的登錄類型:" + loginType);
}
return strategy;
}
}
這裏利用 Spring 的自動裝配,將所有@Service標記的LoginStrategy實現類注入到strategyMap中,鍵為 Bean 名稱(默認是類名首字母小寫,如passwordLoginStrategy),但我們在策略類中通過getLoginType()返回自定義的類型標識,所以需要在配置類中調整 Bean 名稱:
2. 策略 Bean 配置(關鍵!)
@Configuration
publicclass StrategyConfig {
@Bean("passwordStrategy") // 自定義Bean名稱
public LoginStrategy passwordLoginStrategy() {
returnnew PasswordLoginStrategy();
}
@Bean("wechatStrategy")
public LoginStrategy wechatLoginStrategy() {
returnnew WechatLoginStrategy();
}
@Bean("smsStrategy")
public LoginStrategy smsLoginStrategy() {
returnnew SmsLoginStrategy();
}
}
然後在策略類中重寫getLoginType()返回和前端約定的類型標識(如 "password"),並在工廠類中建立類型標識到 Bean 的映射:
// 修改工廠類的構造方法,建立正確映射
public LoginStrategyFactory(Map<String, LoginStrategy> strategyMap) {
this.strategyMap = new HashMap<>();
strategyMap.forEach((beanName, strategy) ->
this.strategyMap.put(strategy.getLoginType(), strategy)
);
}
五、控制器集成:對外提供統一接口
1. 登錄請求參數類
public class LoginRequest {
private String loginType; // 登錄類型,如"password"、"wechat"
private Map<String, Object> params; // 具體參數,不同登錄方式不同
// 省略getter/setter
}
2. 登錄控制器
@RestController
@RequestMapping("/login")
publicclass LoginController {
privatefinal LoginStrategyFactory factory;
@Autowired
public LoginController(LoginStrategyFactory factory) {
this.factory = factory;
}
@PostMapping
public String login(@RequestBody LoginRequest request) {
String loginType = request.getLoginType();
Map<String, Object> params = request.getParams();
LoginStrategy strategy = factory.getStrategy(loginType);
return strategy.execute(params);
}
}
六、測試驗證:三種登錄方式輕鬆切換
1. 用户名密碼登錄請求
{
"loginType": "password",
"params": {
"username": "user123",
"password": "123456"
}
}
2. 微信掃碼登錄請求
{
"loginType": "wechat",
"params": {
"authCode": "wechat_auth_code_456"
}
}
3. 新增登錄方式:比如支付寶登錄
只需新增AlipayLoginStrategy類並實現接口,無需修改現有代碼:
@Service("alipayStrategy")
public class AlipayLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "alipay";
}
@Override
public String execute(Map<String, Object> params) {
// 支付寶登錄邏輯
return "登錄成功(支付寶)";
}
}
七、核心優勢:讓代碼具備 "抗需求變化體質"
1. 策略模式的好處
- 解耦算法: 每種登錄邏輯獨立在策略類中,可讀性強
- 易於擴展: 新增登錄方式只需添加新策略類,符合開閉原則
- 方便測試: 可以單獨測試每個策略類,無需關心其他邏輯
2. 工廠模式的好處
- 封裝創建邏輯: 調用者無需知道策略類的創建細節
- 集中管理實例: 通過 Spring 管理策略 Bean,支持依賴注入和生命週期管理
3. 結合 Spring Boot 的優勢
- 自動裝配: 通過
@Service和Map<String, LoginStrategy>自動收集所有策略 Bean - 類型安全: 工廠類在運行時檢查登錄類型是否合法,避免
NullPointerException
八、最佳實踐:這些細節別忽略
1. 參數校驗前置
在控制器中先對loginType和params做基礎校驗,比如必填參數檢查,避免策略類中重複校驗
2. 公共邏輯抽取
如果多種登錄方式有公共邏輯(如登錄成功後的 Token 生成),可以創建抽象策略類,讓具體策略類繼承
3. 日誌和異常處理
在策略類中添加登錄日誌記錄,統一捕獲異常並返回友好的錯誤信息:
@Service
public class PasswordLoginStrategy implements LoginStrategy {
@Override
public String execute(Map<String, Object> params) {
try {
// 登錄邏輯
} catch (Exception e) {
log.error("用户名密碼登錄失敗:{}", e.getMessage());
throw new LoginException("登錄失敗,請重試"); // 自定義業務異常
}
}
}
4. 配置化登錄類型
將支持的登錄類型存儲在配置文件中,前端調用時先獲取支持的登錄類型列表,避免硬編碼
九、總結:設計模式讓代碼更有 "尊嚴"
回顧三年前的麪條代碼,再看現在的實現,最大的感受是:好的設計模式能讓代碼在需求變化面前保持優雅。工廠模式和策略模式的組合,就像給登錄模塊裝了一個 "熱插拔" 接口,新增功能時不用改核心邏輯,只需要添加新的 "插件"。
如果這篇文章對您有所幫助,或者有所啓發的話,求一鍵三連:點贊、轉發、在看。
關注公眾號:woniuxgg,在公眾號中回覆:筆記 就可以獲得蝸牛為你精心準備的java實戰語雀筆記,回覆面試、開發手冊、有超讚的粉絲福利