
Chain of Responsibility を Java で
Chain of Responsibility は、 振る舞いに関するデザインパターンの一つで、 潜在的なハンドラーの連鎖の上を、 ハンドラーのどれかが処理するまで、 リクエストを回していきます。
このパターンを利用すると、 送り手のクラスと受け手の具象クラスとを結合することなく、 複数のオブジェクトにリクエストを処理する機会を与えることができます。 連鎖は実行時に、 標準のハンドラー・インターフェースに従うハンドラーから動的に構成されます。
複雑度:
人気度:
使用例: Chain of Responsibility パターンは、 Java コードではよく見かけます。
もっともよく見かけるこのパターンの使用例は、 GUI クラス間でイベントを親コンポーネントに向かって遡上させる場合です。 別の特筆すべき使用例としては、 シーケンシャル・アクセス・フィルターがあります。
以下、 Java のコア・ライブラリーでのパターン使用例です:
見つけ方: 共通のインターフェースに従うオブジェクトのグループで、 実作業を行うメソッドが、 別のオブジェクトの同一メソッドを呼ぶことから、 このパターンを識別できます。
アクセスのフィルタリング
この例では、 ユーザー・データを含んだリクエストがどのように、 認証、 承認、 検証などを行うハンドラーの連鎖を通りながら実行されるかを見ていきます。
この例は、 多くの著書に出てくるパターンの典型的な使い方とは少し違っています。 ほとんどのパターンの使用例は、 正当なハンドラーを探し、 実行し、 その後連鎖を抜けます。 しかしここでは、 リクエストは、 そのリクエストを取り扱えないハンドラーに出会うまで、 全ハンドラーが実行します。 流れが若干異なるものの、 これも Chain of Responsibility パターンであることに注意してください。
middleware
middleware/Middleware.java: 基本的な検証インターフェース
package refactoring_guru.chain_of_responsibility.example.middleware; /** * Base middleware class. */ public abstract class Middleware { private Middleware next; /** * Builds chains of middleware objects. */ public static Middleware link(Middleware first, Middleware... chain) { Middleware head = first; for (Middleware nextInChain: chain) { head.next = nextInChain; head = nextInChain; } return first; } /** * 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 refactoring_guru.chain_of_responsibility.example.middleware; /** * ConcreteHandler. Checks whether there are too many failed login requests. */ 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 refactoring_guru.chain_of_responsibility.example.middleware; import refactoring_guru.chain_of_responsibility.example.server.Server; /** * ConcreteHandler. Checks whether a user with the given credentials exists. */ 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 refactoring_guru.chain_of_responsibility.example.middleware; /** * ConcreteHandler. Checks a user's role. */ public class RoleCheckMiddleware 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/Server.java: 認証の対象
package refactoring_guru.chain_of_responsibility.example.server; import refactoring_guru.chain_of_responsibility.example.middleware.Middleware; import java.util.HashMap; import java.util.Map; /** * Server class. */ 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 refactoring_guru.chain_of_responsibility.example; import refactoring_guru.chain_of_responsibility.example.middleware.Middleware; import refactoring_guru.chain_of_responsibility.example.middleware.RoleCheckMiddleware; import refactoring_guru.chain_of_responsibility.example.middleware.ThrottlingMiddleware; import refactoring_guru.chain_of_responsibility.example.middleware.UserExistsMiddleware; import refactoring_guru.chain_of_responsibility.example.server.Server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Demo class. Everything comes together here. */ 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 = Middleware.link( new ThrottlingMiddleware(2), new UserExistsMiddleware(server), new RoleCheckMiddleware() ); // 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); } }
OutputDemo.txt: 実行結果
Enter email: admin@example.com Input password: admin_pass Hello, admin! Authorization have been successful! Enter email: wrong@example.com Input password: wrong_pass This email is not registered! Enter email: wrong@example.com Input password: wrong_pass This email is not registered! Enter email: wrong@example.com Input password: wrong_pass Request limit exceeded!