职责链模式(学习笔记)
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("admin@example.com")) { 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("admin@example.com", "admin_pass"); server.register("user@example.com", "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: admin@example.com Input password: admin_pass Hello, admin! Authorization have been successful! Enter email: user@example.com 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()
识别方法:该模式可通过一组对象的行为方法间接调用其他对象的相同方法来识别,而且所有对象都会遵循相同的接口