重學 Java 設計模式:實戰裝飾器模式(SSO單點登陸功能擴展,增長攔截用戶訪問方法範圍場景)

做者:小傅哥
博客:https://bugstack.cnhtml

沉澱、分享、成長,讓本身和他人都能有所收穫!😄

1、前言

對於代碼你有編程感受嗎java

不少人寫代碼每每是沒有編程感受的,也就是除了能夠把功能按照固定的流程編寫出流水式的代碼外,很難去思考整套功能服務的擴展性和可維護性。尤爲是在一些較大型的功能搭建上,比較缺失一些駕馭能力,從而致使最終的代碼相對來講不能作到盡善盡美。web

江洋大盜與江洋大偷spring

兩個本想描述同樣的意思的詞,只因一字只差就讓人以爲一個是好牛,一個好搞笑。每每咱們去開發編程寫代碼時也常常將一些不恰當的用法用於業務需求實現中,當卻不能意識到。一方面是因爲編碼很少缺乏較大型項目的實踐,另外一方面是不思進取的總在以完成需求爲目標缺乏精益求精的工匠精神。編程

書歷來不是看的而是用的設計模式

在這個學習資料幾乎爆炸的時代,甚至你能夠輕易就獲取幾個T的視頻,小手輕輕一點就收藏一堆文章,但卻不多去看。學習的過程從不僅是簡單的看一遍就能夠,對於一些實操性的技術書籍,若是真的但願學習到知識,那麼必定是把這本書用起來而絕對不是看起來。安全

2、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,能夠經過關注公衆號bugstack蟲洞棧,回覆源碼下載獲取(打開獲取的連接,找到序號18)
工程 描述
itstack-demo-design-9-00 場景模擬工程;模擬單點登陸類
itstack-demo-design-9-01 使用一坨代碼實現業務需求
itstack-demo-design-9-02 經過設計模式優化改造代碼,產生對比性從而學習

3、裝飾器模式介紹

裝飾器模式,圖片來自 refactoringguru.cn

初看上圖感受裝飾器模式有點像俄羅斯套娃、某衆汽車🚕,而裝飾器的核心就是再不改原有類的基礎上給類新增功能。不改變原有類,可能有的小夥伴會想到繼承、AOP切面,固然這些方式均可以實現,可是使用裝飾器模式會是另一種思路更爲靈活,能夠避免繼承致使的子類過多,也能夠避免AOP帶來的複雜性。微信

你熟悉的場景不少用到裝飾器模式cookie

new BufferedReader(new FileReader(""));,這段代碼你是否熟悉,相信學習java開發到字節流、字符流、文件流的內容時都見到了這樣的代碼,一層嵌套一層,一層嵌套一層,字節流轉字符流等等,而這樣方式的使用就是裝飾器模式的一種體現。併發

4、案例場景模擬

場景模擬;單點登陸功能擴展

在本案例中咱們模擬一個單點登陸功能擴充的場景

通常在業務開發的初期,每每內部的ERP使用只須要判斷帳戶驗證便可,驗證經過後便可訪問ERP的全部資源。但隨着業務的不斷髮展,團隊裏開始出現專門的運營人員、營銷人員、數據人員,每一個人員對於ERP的使用需求不一樣,有些須要建立活動,有些只是查看數據。同時爲了保證數據的安全性,不會讓每一個用戶都有最高的權限。

那麼以往使用的SSO是一個組件化通用的服務,不能在裏面添加須要的用戶訪問驗證功能。這個時候咱們就可使用裝飾器模式,擴充原有的單點登陸服務。但同時也保證原有功能不受破壞,能夠繼續使用。

1. 場景模擬工程

itstack-demo-design-9-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── HandlerInterceptor.java
                └── SsoInterceptor.java
  • 這裏模擬的是spring中的類:HandlerInterceptor,實現起接口功能SsoInterceptor模擬的單點登陸攔截服務。
  • 爲了不引入太多spring的內容影響對設計模式的閱讀,這裏使用了同名的類和方法,儘量減小外部的依賴。

2. 場景簡述

2.1 模擬Spring的HandlerInterceptor

public interface HandlerInterceptor {

    boolean preHandle(String request, String response, Object handler);

}
  • 實際的單點登陸開發會基於;org.springframework.web.servlet.HandlerInterceptor 實現。

2.2 模擬單點登陸功能

public class SsoInterceptor implements HandlerInterceptor{

    public boolean preHandle(String request, String response, Object handler) {
        // 模擬獲取cookie
        String ticket = request.substring(1, 8);
        // 模擬校驗
        return ticket.equals("success");
    }

}
  • 這裏的模擬實現很是簡單只是截取字符串,實際使用須要從HttpServletRequest request對象中獲取cookie信息,解析ticket值作校驗。
  • 在返回的裏面也很是簡單,只要獲取到了success就認爲是容許登陸。

5、用一坨坨代碼實現

此場景大多數實現的方式都會採用繼承類

繼承類的實現方式也是一個比較通用的方式,經過繼承後重寫方法,併發將本身的邏輯覆蓋進去。若是是一些簡單的場景且不須要不斷維護和擴展的,此類實現並不會有什麼,也不會致使子類過多。

1. 工程結構

itstack-demo-design-9-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── LoginSsoDecorator.java
  • 以上工程結構很是簡單,只是經過 LoginSsoDecorator 繼承 SsoInterceptor,重寫方法功能。

2. 代碼實現

public class LoginSsoDecorator extends SsoInterceptor {

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        // 模擬獲取cookie
        String ticket = request.substring(1, 8);
        // 模擬校驗
        boolean success = ticket.equals("success");

        if (!success) return false;

        String userId = request.substring(9);
        String method = authMap.get(userId);

        // 模擬方法校驗
        return "queryUserInfo".equals(method);
    }

}
  • 以上這部分經過繼承重寫方法,將我的可訪問哪些方法的功能添加到方法中。
  • 以上看着代碼還算比較清晰,但若是是比較複雜的業務流程代碼,就會很混亂。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator();
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登陸校驗:" + request + (success ? " 放行" : " 攔截"));
}
  • 這裏模擬的至關於登陸過程當中的校驗操做,判斷用戶是否可登陸以及是否可訪問方法。

3.2 測試結果

登陸校驗:1successhuahua 攔截

Process finished with exit code 0
  • 從測試結果來看知足咱們的預期,已經作了攔截。若是你在學習的過程當中,能夠嘗試模擬單點登陸並繼承擴展功能。

6、裝飾器模式重構代碼

接下來使用裝飾器模式來進行代碼優化,也算是一次很小的重構。

裝飾器主要解決的是直接繼承下因功能的不斷橫向擴展致使子類膨脹的問題,而是用裝飾器模式後就會比直接繼承顯得更加靈活同時這樣也就再也不須要考慮子類的維護。

在裝飾器模式中有四個比較重要點抽象出來的點;

  1. 抽象構件角色(Component) - 定義抽象接口
  2. 具體構件角色(ConcreteComponent) - 實現抽象接口,能夠是一組
  3. 裝飾角色(Decorator) - 定義抽象類並繼承接口中的方法,保證一致性
  4. 具體裝飾角色(ConcreteDecorator) - 擴展裝飾具體的實現邏輯

經過以上這四項來實現裝飾器模式,主要核心內容會體如今抽象類的定義和實現上。

1. 工程結構

itstack-demo-design-9-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── LoginSsoDecorator.java
                └── SsoDecorator.java

裝飾器模式模型結構

裝飾器模式模型結構

  • 以上是一個裝飾器實現的類圖結構,重點的類是SsoDecorator,這個類是一個抽象類主要完成了對接口HandlerInterceptor繼承。
  • 當裝飾角色繼承接口後會提供構造函數,入參就是繼承的接口實現類便可,這樣就能夠很方便的擴展出不一樣功能組件。

2. 代碼實現

2.1 抽象類裝飾角色

public abstract class SsoDecorator implements HandlerInterceptor {

    private HandlerInterceptor handlerInterceptor;

    private SsoDecorator(){}

    public SsoDecorator(HandlerInterceptor handlerInterceptor) {
        this.handlerInterceptor = handlerInterceptor;
    }

    public boolean preHandle(String request, String response, Object handler) {
        return handlerInterceptor.preHandle(request, response, handler);
    }

}
  • 在裝飾類中有兩個重點的地方是;1)繼承了處理接口、2)提供了構造函數、3)覆蓋了方法preHandle
  • 以上三個點是裝飾器模式的核心處理部分,這樣能夠踢掉對子類繼承的方式實現邏輯功能擴展。

2.2 裝飾角色邏輯實現

public class LoginSsoDecorator extends SsoDecorator {

    private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
        super(handlerInterceptor);
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        boolean success = super.preHandle(request, response, handler);
        if (!success) return false;
        String userId = request.substring(8);
        String method = authMap.get(userId);
        logger.info("模擬單點登陸方法訪問攔截校驗:{} {}", userId, method);
        // 模擬方法校驗
        return "queryUserInfo".equals(method);
    }
}
  • 在具體的裝飾類實現中,繼承了裝飾類SsoDecorator,那麼如今就能夠擴展方法;preHandle
  • preHandle的實現中能夠看到,這裏只關心擴展部分的功能,同時不會影響原有類的核心服務,也不會由於使用繼承方式而致使的多餘子類,增長了總體的靈活性。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登陸校驗:" + request + (success ? " 放行" : " 攔截"));
}
  • 這裏測試了對裝飾器模式的使用,經過透傳原有單點登陸類new SsoInterceptor(),傳遞給裝飾器,讓裝飾器能夠執行擴充的功能。
  • 同時對於傳遞者和裝飾器均可以是多組的,在一些實際的業務開發中,每每也是因爲太多類型的子類實現而致使不易於維護,從而使用裝飾器模式替代。

3.2 測試結果

23:50:50.796 [main] INFO  o.i.demo.design.LoginSsoDecorator - 模擬單點登陸方法訪問攔截校驗:huahua queryUserInfo
登陸校驗:1successhuahua 放行

Process finished with exit code 0
  • 結果符合預期,擴展了對方法攔截的校驗性。
  • 若是你在學習的過程當中有用到過單點登錄,那麼能夠適當在裏面進行擴展裝飾器模式進行學習使用。
  • 另外,還有一種場景也可使用裝飾器。例如;你以前使用某個實現某個接口接收單個消息,但因爲外部的升級變爲發送list集合消息,但你又不但願全部的代碼類都去修改這部分邏輯。那麼可使用裝飾器模式進行適配list集合,給使用者依然是for循環後的單個消息。

7、總結

  • 使用裝飾器模式知足單一職責原則,你能夠在本身的裝飾類中完成功能邏輯的擴展,而不影響主類,同時能夠按需在運行時添加和刪除這部分邏輯。另外裝飾器模式與繼承父類重寫方法,在某些時候須要按需選擇,並不必定某一個就是最好。
  • 裝飾器實現的重點是對抽象類繼承接口方式的使用,同時設定被繼承的接口能夠經過構造函數傳遞其實現類,由此增長擴展性並重寫方法裏能夠實現此部分父類實現的功能。
  • 就像夏天熱你穿短褲,冬天冷你穿棉褲,雨天挨澆你穿雨衣同樣,你的根本自己沒有被改變,而你的需求卻被不一樣的裝飾而實現。生活中每每比比皆是設計,當你能夠融合這部分活靈活現的例子到代碼實現中,每每會創造出更加優雅的實現方式。

8、推薦閱讀

相關文章
相關標籤/搜索