1. 意圖
使多個物件都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系,將這些物件連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個物件處理它為止
2. 動機
假設現在開發一個在線訂購系統,希望對系統訪問進行限制,只允許認證用戶創建訂單,可是,在接下來的幾個月里,不斷有新的需求提了出來....
- 一位同事認為直接將原始資料傳遞給訂購系統存在安全隱患,因此你新增了額外的驗證步驟來清洗請求中的資料
- 后來,有人注意到系統無法抵御暴力密碼破解方式的攻擊,為了防范這種情況,你立刻添加了一個檢查步驟來過濾來自同一IP地址的重復錯誤請求
- 再后來,又有人提議可以對包含同樣資料的重復請求回傳快取中的結果,從而提高系統回應速度,因此,你新增了一個檢查步驟,確保只有沒有滿足條件的快取結果時請求才能通過并被發送給系統
檢查代碼本來就已經混亂不堪,而每次新增功能都會使其更加臃腫,修改某個檢查步驟有時會影響其他的檢查步驟,系統會變得讓人非常費解,而且其維護成本也會激增
責任鏈會將特定行為轉換為被稱作處理者的獨立物件,每個檢查步驟都可被抽取為僅有單個方法的類,并執行檢查操作,請求及其資料則會被作為引數傳遞給該方法,并且,鏈上的每個處理者都有一個成員變數來保存對于下一處理者的參考,處理者接收到請求后自行決定是否能夠對其進行處理,如果自己能夠處理,處理者就不再繼續傳遞請求,否則,請求會在鏈上移動, 直至所有處理者都有機會對其進行處理,因此在這種情況下,每個請求要么最多有一個處理者對其進行處理,要么沒有任何處理者對其進行處理
3. 適用性
- 當程式需要使用不同方式處理不同種類請求,而且請求型別和順序預先未知時,該模式能將多個處理者連接成一條鏈,接收到請求后,它會 “詢問” 每個處理者是否能夠對其進行處理,這樣所有處理者都有機會來處理請求
- 當必須按順序執行多個處理者時
- 如果所需處理者及其順序必須在運行時進行改變
4. 結構
5. 效果
1)單一職責原則, 可對發起操作和執行操作的類進行解耦
2)開閉原則, 可以在不更改現有代碼的情況下在程式中新增處理者
3)增強了給物件指派職責的靈活性
4)請求可能一直到鏈的末端都得不到處理
6. 代碼實作
middleware/Middleware.java: 基礎驗證介面
package chain_of_responsibility.middleware; /** * @author GaoMing * @date 2021/7/21 - 21:16 */ public abstract class Middleware { private Middleware next; /** * Builds chains of middleware objects. */ public Middleware linkWith(Middleware next) { this.next = next; return next; } /** * Subclasses will implement this method with concrete checks. */ public abstract boolean check(String email, String password); /** * Runs check on the next object in chain or ends traversing if we're in * last object in chain. */ protected boolean checkNext(String email, String password) { if (next == null) { return true; } return next.check(email, password); } }
middleware/ThrottlingMiddleware.java: 檢查請求數量限制
package chain_of_responsibility.middleware; /** * @author GaoMing * @date 2021/7/21 - 21:16 */ public class ThrottlingMiddleware extends Middleware{ private int requestPerMinute; private int request; private long currentTime; public ThrottlingMiddleware(int requestPerMinute) { this.requestPerMinute = requestPerMinute; this.currentTime = System.currentTimeMillis(); } /** * Please, not that checkNext() call can be inserted both in the beginning * of this method and in the end. * * This gives much more flexibility than a simple loop over all middleware * objects. For instance, an element of a chain can change the order of * checks by running its check after all other checks. */ public boolean check(String email, String password) { if (System.currentTimeMillis() > currentTime + 60_000) { request = 0; currentTime = System.currentTimeMillis(); } request++; if (request > requestPerMinute) { System.out.println("Request limit exceeded!"); Thread.currentThread().stop(); } return checkNext(email, password); } }
middleware/UserExistsMiddleware.java: 檢查用戶登錄資訊
package chain_of_responsibility.middleware; import chain_of_responsibility.server.Server; /** * @author GaoMing * @date 2021/7/21 - 21:16 */ public class UserExistsMiddleware extends Middleware{ private Server server; public UserExistsMiddleware(Server server) { this.server = server; } public boolean check(String email, String password) { if (!server.hasEmail(email)) { System.out.println("This email is not registered!"); return false; } if (!server.isValidPassword(email, password)) { System.out.println("Wrong password!"); return false; } return checkNext(email, password); } }
middleware/RoleCheckMiddleware.java: 檢查用戶角色
package chain_of_responsibility.middleware; /** * @author GaoMing * @date 2021/7/21 - 21:17 */ public class RockCheckMiddleware extends Middleware{ public boolean check(String email, String password) { if (email.equals("[email protected]")) { System.out.println("Hello, admin!"); return true; } System.out.println("Hello, user!"); return checkNext(email, password); } }
server/Server.java: 授權目標
package chain_of_responsibility.server; import chain_of_responsibility.middleware.Middleware; import java.util.HashMap; import java.util.Map; /** * @author GaoMing * @date 2021/7/21 - 21:22 */ public class Server { private Map<String, String> users = new HashMap<>(); private Middleware middleware; /** * Client passes a chain of object to server. This improves flexibility and * makes testing the server class easier. */ public void setMiddleware(Middleware middleware) { this.middleware = middleware; } /** * Server gets email and password from client and sends the authorization * request to the chain. */ public boolean logIn(String email, String password) { if (middleware.check(email, password)) { System.out.println("Authorization have been successful!"); // Do something useful here for authorized users. return true; } return false; } public void register(String email, String password) { users.put(email, password); } public boolean hasEmail(String email) { return users.containsKey(email); } public boolean isValidPassword(String email, String password) { return users.get(email).equals(password); } }
Demo.java: 客戶端代碼
package chain_of_responsibility; import chain_of_responsibility.middleware.Middleware; import chain_of_responsibility.middleware.RockCheckMiddleware; import chain_of_responsibility.middleware.ThrottlingMiddleware; import chain_of_responsibility.middleware.UserExistsMiddleware; import chain_of_responsibility.server.Server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author GaoMing * @date 2021/7/21 - 21:15 */ public class Demo { private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private static Server server; private static void init() { server = new Server(); server.register("[email protected]", "admin_pass"); server.register("[email protected]", "user_pass"); // All checks are linked. Client can build various chains using the same // components. Middleware middleware = new ThrottlingMiddleware(2); middleware.linkWith(new UserExistsMiddleware(server)) .linkWith(new RockCheckMiddleware()); // Server gets a chain from client code. server.setMiddleware(middleware); } public static void main(String[] args) throws IOException { init(); boolean success; do { System.out.print("Enter email: "); String email = reader.readLine(); System.out.print("Input password: "); String password = reader.readLine(); success = server.logIn(email, password); } while (!success); } }
執行結果
Enter email: [email protected] Input password: admin_pass Hello, admin! Authorization have been successful! Enter email: [email protected] Input password: user_pass Hello, user! Authorization have been successful!
7. 與其他模式的關系
-
責任鏈模式、命令模式、中介者模式和觀察者模式用于處理請求發送者和接收者之間的不同連接方式:
1. 責任鏈按照順序將請求動態傳遞給一系列的潛在接收者,直至其中一名接收者對請求進行處理
2. 命令在發送者和請求者之間建立單向連接
3. 中介者清除了發送者和請求者之間的直接連接, 強制它們通過一個中介物件進行間接溝通
4. 觀察者允許接收者動態地訂閱或取消接收請求 - 責任鏈通常和組合模式結合使用,在這種情況下,葉組件接收到請求后,可以將請求沿包含全體父組件的鏈一直傳遞至物件樹的底部
例如,當用戶點擊按鈕時,按鈕產生的事件將沿著GUI元素鏈進行傳遞,最開始是按鈕的容器(如表單或面板),直至應用程式主視窗,鏈上第一個能處理該事件的元素會對其進行處理 
- 在責任鏈中,可以使用命令模式封裝請求
8. 已知應用
使用示例:責任鏈模式在Java程式中并不常見,因為它僅在代碼與物件鏈打交道時才能發揮作用,該模式最流行的使用案例之一是在GUI類中將事件向上傳遞給父組件,另一個值得注意的使用案例是依次訪問過濾器
下面是該模式在核心 Java 程式庫中的一些示例:
- javax.servlet.Filter#doFilter()
- java.util.logging.Logger#log()
識別方法:該模式可通過一組物件的行為方法間接呼叫其他物件的相同方法來識別,而且所有物件都會遵循相同的介面
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/308446.html
標籤:設計模式
上一篇:代理模式(學習筆記)
下一篇:中介者模式(學習筆記)
