首頁>技術>

策略模式作為一種軟體設計模式,指物件有某個行為,但是在不同的場景中,該行為有不同的實現演算法,可以替代程式碼中大量的 if-else。

那麼在什麼時候使用策略模式呢?

在《阿里巴巴Java開發手冊》中有提到當超過 3 層的 if-else 的邏輯判斷程式碼可以使用策略模式來實現。

在 Spring 中實現策略模式的方式有很多種,下面透過一個案例來演示下,比如有個需求需要實現支援第三方登入,目前需要支援以下三種登入方式:

策略模式結構如下圖所示:

策略模式結構

主要包括一個登入介面類和幾種登入方式的實現方式,並利用簡單工廠來獲取對應的處理器。

定義策略介面

首先定義一個登入的策略介面 LoginHandler,其中包括兩個方法:

獲取策略型別的方法處理策略邏輯的方法
public interface LoginHandler<T extends Serializable> {    /**     * 獲取登入型別     *     * @return     */    LoginType getLoginType();    /**     * 登入     *     * @param request     * @return     */    LoginResponse<String, T> handleLogin(LoginRequest request);}

其中,LoginHandler 的 getLoginType 方法用來獲取登入的型別(即策略型別),用於根據客戶端傳遞的引數直接獲取到對應的策略實現。

客戶端傳遞的相關引數都被封裝為 LoginRequest,傳遞給 handleLogin 進行處理。

@Datapublic class LoginRequest {    private LoginType loginType;    private Long userId;}

其中,根據需求定義登入型別列舉如下:

public enum LoginType {    QQ,    WE_CHAT,    WEI_BO;}
實現策略介面

在定義好策略介面後,我們就需要根據各種第三方登入來實現對應的處理邏輯就可以了。

微信登入
@Componentpublic class WeChatLoginHandler implements LoginHandler<String> {    private final Logger logger = LoggerFactory.getLogger(this.getClass());    /**     * 獲取登入型別     *     * @return     */    @Override    public LoginType getLoginType() {        return LoginType.WE_CHAT;    }    /**     * 登入     *     * @param request     * @return     */    @Override    public LoginResponse<String, String> handleLogin(LoginRequest request) {        logger.info("微信登入:userId:{}", request.getUserId());        String weChatName = getWeChatName(request);        return LoginResponse.success("微信登入成功", weChatName);    }    private String getWeChatName(LoginRequest request) {        return "wupx";    }}
QQ 登入
@Componentpublic class QQLoginHandler implements LoginHandler<Serializable> {    private final Logger logger = LoggerFactory.getLogger(this.getClass());    /**     * 獲取登入型別     *     * @return     */    @Override    public LoginType getLoginType() {        return LoginType.QQ;    }    /**     * 登入     *     * @param request     * @return     */    @Override    public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {        logger.info("QQ登入:userId:{}", request.getUserId());        return LoginResponse.success("QQ登入成功", null);    }}
微博登入
@Componentpublic class WeiBoLoginHandler implements LoginHandler<Serializable> {    private final Logger logger = LoggerFactory.getLogger(this.getClass());    /**     * 獲取登入型別     *     * @return     */    @Override    public LoginType getLoginType() {        return LoginType.WEI_BO;    }    /**     * 登入     *     * @param request     * @return     */    @Override    public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {        logger.info("微博登入:userId:{}", request.getUserId());        return LoginResponse.success("微博登入成功", null);    }}
建立策略的簡單工廠
@Componentpublic class LoginHandlerFactory implements InitializingBean, ApplicationContextAware {    private static final Map<LoginType, LoginHandler<Serializable>> LOGIN_HANDLER_MAP = new EnumMap<>(LoginType.class);    private ApplicationContext appContext;    /**     * 根據登入型別獲取對應的處理器     *     * @param loginType 登入型別     * @return 登入型別對應的處理器     */    public LoginHandler<Serializable> getHandler(LoginType loginType) {        return LOGIN_HANDLER_MAP.get(loginType);    }    @Override    public void afterPropertiesSet() throws Exception {        // 將 Spring 容器中所有的 LoginHandler 註冊到 LOGIN_HANDLER_MAP        appContext.getBeansOfType(LoginHandler.class)                .values()                .forEach(handler -> LOGIN_HANDLER_MAP.put(handler.getLoginType(), handler));    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        appContext = applicationContext;    }}

我們讓 LoginHandlerFactory實現 InitializingBean 介面,在 afterPropertiesSet 方法中,基於 Spring 容器將所有 LoginHandler 自動註冊到 LOGIN_HANDLER_MAP,從而 Spring 容器啟動完成後, getHandler 方法可以直接透過 loginType 來獲取對應的登入處理器。

建立登入服務

在登入服務中,我們透過 LoginHandlerFactory 來獲取對應的登入處理器,從而處理不同型別的第三方登入:

@Servicepublic class LoginServiceImpl implements LoginService {    @Autowired    private LoginHandlerFactory loginHandlerFactory;    @Override    public LoginResponse<String, Serializable> login(LoginRequest request) {        LoginType loginType = request.getLoginType();        // 根據 loginType 找到對應的登入處理器        LoginHandler<Serializable> loginHandler =                loginHandlerFactory.getHandler(loginType);        // 處理登入        return loginHandler.handleLogin(request);    }}

Factory 只負責獲取 Handler,Handler 只負責處理具體的登入,Service 只負責邏輯編排,從而達到功能上的低耦合高內聚。

測試

寫一個 Controller:

@RestControllerpublic class LoginController {    @Autowired    private LoginService loginService;    /**     * 登入     */    @PostMapping("/login")    public LoginResponse<String, Serializable> login(@RequestParam LoginType loginType, @RequestParam Long userId) {        LoginRequest loginRequest = new LoginRequest();        loginRequest.setLoginType(loginType);        loginRequest.setUserId(userId);        return loginService.login(loginRequest);    }}

然後用 Postman 測下下:

QQ登入

是不是很簡單呢?如果需求又要加需求,需要支援 GitHub 第三方登入。

此時我們只需要新增一個新的策略實現,然後在登入列舉中加入對應的型別即可:

@Componentpublic class GitHubLoginHandler implements LoginHandler<Serializable> {    private final Logger logger = LoggerFactory.getLogger(this.getClass());    /**     * 獲取登入型別     *     * @return     */    @Override    public LoginType getLoginType() {        return LoginType.GIT_HUB;    }    /**     * 登入     *     * @param request     * @return     */    @Override    public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {        logger.info("GitHub登入:userId:{}", request.getUserId());        return LoginResponse.success("GitHub登入成功", null);    }}

此時不需要修改任何程式碼 ,因為 Spring 容器重啟時會自動將 GitHubLoginHandler註冊到 LoginHandlerFactory 中,使用 Spring 實現策略模式就是這麼簡單,還不快學起來!

12
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 樹莓派ubuntu配置開機自動啟動node-red