天天看點

重學 Java 設計模式:實戰裝飾器模式(SSO單點登入功能擴充,增加攔截使用者通路方法範圍場景)

作者:小傅哥

部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

對于代碼你有程式設計感覺嗎

很多人寫代碼往往是沒有程式設計感覺的,也就是除了可以把功能按照固定的流程編寫出流水式的代碼外,很難去思考整套功能服務的擴充性和可維護性。尤其是在一些較大型的功能搭建上,比較缺失一些駕馭能力,進而導緻最終的代碼相對來說不能做到盡善盡美。

江洋大盜與江洋大偷

兩個本想描述一樣的意思的詞,隻因一字隻差就讓人覺得一個是好牛,一個好搞笑。往往我們去開發程式設計寫代碼時也經常将一些不恰當的用法用于業務需求實作中,當卻不能意識到。一方面是由于編碼不多缺少較大型項目的實踐,另一方面是不思進取的總在以完成需求為目标缺少精益求精的工匠精神。

書從來不是看的而是用的

在這個學習資料幾乎爆炸的時代,甚至你可以輕易就擷取幾個T的視訊,小手輕輕一點就收藏一堆文章,但卻很少去看。學習的過程從不隻是簡單的看一遍就可以,對于一些實操性的技術書籍,如果真的希望學習到知識,那麼一定是把這本書用起來而絕對不是看起來。

二、開發環境

  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 通過設計模式優化改造代碼,産生對比性進而學習

三、裝飾器模式介紹

重學 Java 設計模式:實戰裝飾器模式(SSO單點登入功能擴充,增加攔截使用者通路方法範圍場景)

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

你熟悉的場景很多用到裝飾器模式

new BufferedReader(new FileReader(""));

,這段代碼你是否熟悉,相信學習java開發到位元組流、字元流、檔案流的内容時都見到了這樣的代碼,一層嵌套一層,一層嵌套一層,位元組流轉字元流等等,而這樣方式的使用就是裝飾器模式的一種展現。

四、案例場景模拟

重學 Java 設計模式:實戰裝飾器模式(SSO單點登入功能擴充,增加攔截使用者通路方法範圍場景)

在本案例中我們模拟一個單點登入功能擴充的場景

一般在業務開發的初期,往往内部的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

    就認為是允許登入。

五、用一坨坨代碼實作

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

繼承類的實作方式也是一個比較通用的方式,通過繼承後重寫方法,并發将自己的邏輯覆寫進去。如果是一些簡單的場景且不需要不斷維護和擴充的,此類實作并不會有什麼,也不會導緻子類過多。

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
           
  • 從測試結果來看滿足我們的預期,已經做了攔截。如果你在學習的過程中,可以嘗試模拟單點登入并繼承擴充功能。

六、裝飾器模式重構代碼

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

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

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

  1. 抽象構件角色(Component) -

    定義抽象接口

  2. 具體構件角色(ConcreteComponent) -

    實作抽象接口,可以是一組

  3. 裝飾角色(Decorator) -

    定義抽象類并繼承接口中的方法,保證一緻性

  4. 具體裝飾角色(ConcreteDecorator) -

    擴充裝飾具體的實作邏輯

通過以上這四項來實作裝飾器模式,主要核心内容會展現在抽象類的定義和實作上。

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

裝飾器模式模型結構

重學 Java 設計模式:實戰裝飾器模式(SSO單點登入功能擴充,增加攔截使用者通路方法範圍場景)
  • 以上是一個裝飾器實作的類圖結構,重點的類是

    SsoDecorator

    ,這個類是一個抽象類主要完成了對接口

    HandlerInterceptor

    繼承。
  • 當裝飾角色繼承接口後會提供構造函數,入參就是繼承的接口實作類即可,這樣就可以很友善的擴充出不同功能元件。

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

    的實作中可以看到,這裡隻關心擴充部分的功能,同時不會影響原有類的核心服務,也不會因為使用繼承方式而導緻的多餘子類,增加了整體的靈活性。

@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()

    ,傳遞給裝飾器,讓裝飾器可以執行擴充的功能。
  • 同時對于傳遞者和裝飾器都可以是多組的,在一些實際的業務開發中,往往也是由于太多類型的子類實作而導緻不易于維護,進而使用裝飾器模式替代。

23:50:50.796 [main] INFO  o.i.demo.design.LoginSsoDecorator - 模拟單點登入方法通路攔截校驗:huahua queryUserInfo
登入校驗:1successhuahua 放行

Process finished with exit code 0
           
  • 結果符合預期,擴充了對方法攔截的校驗性。
  • 如果你在學習的過程中有用到過單點登陸,那麼可以适當在裡面進行擴充裝飾器模式進行學習使用。
  • 另外,還有一種場景也可以使用裝飾器。例如;你之前使用某個實作某個接口接收單個消息,但由于外部的更新變為發送

    list

    集合消息,但你又不希望所有的代碼類都去修改這部分邏輯。那麼可以使用裝飾器模式進行适配

    list

    集合,給使用者依然是

    for

    循環後的單個消息。

七、總結

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

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙叢集更新場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰橋接模式(多支付管道「微信、支付寶」與多支付模式「刷臉、指紋」場景)
  • 6. 重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!

繼續閱讀