Trong các viết trước chúng ta đã cùng tìm hiểu về các Design Pattern thuộc nhóm cấu trúc (Structuaral Pattern). Trong bài viết hôm nay chúng ta sẽ cùng tìm hiểu một Pattern thuộc nhóm hành vi (Behavior Pattern) là Chain of Responsibility.
Toàn bộ các mẫu hành vi (Behavior Pattern) xoay quanh nguyên tắc “thành phần đối tượng (composition) hơn là thừa kế (inheritance)”. Nguyên tắc này nói rằng, thay vì mở rộng từ một lớp hiện có, thiết kế lớp của bạn để tham chiếu một lớp hiện có mà bạn muốn sử dụng. Trong Java, nó được thực hiện bằng cách khai báo một biến tham chiếu đối tượng của lớp hiện có. Sau đó, bằng cách khởi tạo đối tượng thông qua hàm tạo (constructor) hoặc phương thức (setter), và cuối cùng sử dụng đối tượng được tham chiếu.
Nội dung
Chain of Responsibility Pattern là gì?
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along with the chain until an object handles it.
Chain of Responsibility (COR) là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern).
Chain of Responsiblity cho phép một đối tượng gửi một yêu cầu nhưng không biết đối tượng nào sẽ nhận và xử lý nó. Điều này được thực hiện bằng cách kết nối các đối tượng nhận yêu cầu thành một chuỗi (chain) và gửi yêu cầu theo chuỗi đó cho đến khi có một đối tượng xử lý nó.
Chain of Responsibility Pattern hoạt động như một danh sách liên kết (Linked list) với việc đệ quy duyệt qua các phần tử (recursive traversal).
Cài đặt Chain of Responsibility Pattern như thế nào?
Các thành phần tham gia mẫu Chain of Responsibility:
- Handler : định nghĩa 1 interface để xử lý các yêu cầu. Gán giá trị cho đối tượng successor (không bắt buộc).
- ConcreteHandler : xử lý yêu cầu. Có thể truy cập đối tượng successor (thuộc class Handler). Nếu đối tượng ConcreateHandler không thể xử lý được yêu cầu, nó sẽ gởi lời yêu cầu cho successor của nó.
- Client : tạo ra các yêu cầu và yêu cầu đó sẽ được gửi đến các đối tượng tiếp nhận.
Client gửi một yêu cầu để được xử lý gửi nó đến chuỗi (chain) các trình xử lý (handers), đó là các lớp mở rộng lớp Handler. Mỗi Hanlder trong chuỗi lần lượt cố gắng xử lý yêu cầu nhận được từ Client. Nếu trình xử lý đầu tiên (ConcreteHandler) có thể xử lý nó, thì yêu cầu sẽ được xử lý. Nếu không được xử lý thì sẽ gửi đến trình xử lý tiếp theo trong chuỗi (ConcreteHandler + 1).
Các bạn có thể tham khảo cách implement về Chain of Responsibility trong các thư viện của Java như:
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
Ví dụ Chain of Responsibility Pattern với ứng dụng Logger
Trong ví dụ này, chúng ta sẽ tự xây dựng hệ thống Logger cho riêng mình như sau:
- Logger: là một abstract class Handler, cho phép thực hiện một chain logger dựa vào giá trị LogLevel ứng với từng Handler. Nếu mức độ lỗi (severity) lớn hơn hoặc bằng với LogLevel mà nó có thể handle thì sẽ thực hiện writeMessage(), đồng thời gọi Hanlder kế tiếp nếu có.
- ConsoleLogger, FileLogger, EmailLogger: đây là các ConcreteHandler, nó xác định LogLevel mà nó có thể xử lý, và cài đặt phương thức writeMessage() cho riêng nó.
- Client : sử dụng Logger để ghi log.
- LogLevel : là một enum (constant), dùng để xác định các mức độ ghi log.
- AppLogger: là một lớp tiện ích, tạo chuỗi Handler để xử lý ghi log.
LogLevel.java
package com.gpcoder.patterns.behavioral.cor.logger; public enum LogLevel { NONE(0), INFO(1), DEBUG(2), WARNING(4), ERROR(8), FATAL(16), ALL(32); private int level; private LogLevel(int level) { this.level = level; } public int getLevel() { return level; } }
Logger.java
package com.gpcoder.patterns.behavioral.cor.logger; public abstract class Logger { protected LogLevel logLevel; protected Logger nextlogger; // The next Handler in the chain public Logger(LogLevel logLevel) { this.logLevel = logLevel; } // Set the next logger to make a list/chain of Handlers. public Logger setNext(Logger nextlogger) { this.nextlogger = nextlogger; return nextlogger; } public void log(LogLevel severity, String msg) { if (logLevel.getLevel() <= severity.getLevel()) { writeMessage(msg); } if (nextlogger != null) { nextlogger.log(severity, msg); } } protected abstract void writeMessage(String msg); }
ConsoleLogger.java
package com.gpcoder.patterns.behavioral.cor.logger; public class ConsoleLogger extends Logger { public ConsoleLogger(LogLevel logLevel) { super(logLevel); } @Override protected void writeMessage(String msg) { System.out.println("Console logger: " + msg); } }
FileLogger.java
package com.gpcoder.patterns.behavioral.cor.logger; public class FileLogger extends Logger { public FileLogger(LogLevel logLevel) { super(logLevel); } @Override protected void writeMessage(String msg) { System.out.println("File logger: " + msg); } }
EmailLogger.java
package com.gpcoder.patterns.behavioral.cor.logger; public class EmailLogger extends Logger { public EmailLogger(LogLevel logLevel) { super(logLevel); } @Override protected void writeMessage(String msg) { System.out.println("Email logger: " + msg); } }
AppLogger.java
package com.gpcoder.patterns.behavioral.cor.logger; public class AppLogger { public static Logger getLogger() { Logger consoleLogger = new ConsoleLogger(LogLevel.DEBUG); Logger fileLogger = consoleLogger.setNext(new FileLogger(LogLevel.ERROR)); fileLogger.setNext(new EmailLogger(LogLevel.FATAL)); return consoleLogger; } }
Client.java
package com.gpcoder.patterns.behavioral.cor.logger; /** * Chain of Responsibility Pattern Examle * * @author gpcoder.com */ public class Client { public static void main(String[] args) { // Build the chain of responsibility Logger logger = AppLogger.getLogger(); // Handled by ConsoleLogger since the console has a LogLevel of DEBUG logger.log(LogLevel.INFO, "Info message"); logger.log(LogLevel.DEBUG, "Debug message"); // Handled by ConsoleLogger and FileLogger logger.log(LogLevel.ERROR, "Error message"); // Handled by ConsoleLogger, FileLogger, EmailLogger logger.log(LogLevel.FATAL, "Factal message"); } }
Output của chương trình:
Console logger: Debug message Console logger: Error message File logger: Error message Console logger: Factal message File logger: Factal message Email logger: Factal message
Ở ví dụ này, chain của chúng ta sẽ kết thúc khi tất cả các đối tượng trong chain có thể xử lý đã xử lý.
Ví dụ Chain of Responsibility Pattern với ứng dụng LeaveRequest
Một ví dụ khác là ứng dụng phê duyệt xin nghỉ phép. Nếu xin nghỉ <=3 ngày thì Supervisor có thể phê duyệt (approve). Nếu xin nghỉ <=5 ngày thì DeliveryManager có thể approve. Nếu xin nghỉ >5 ngày thì phải được approve bởi Director. Quy trình này có thể linh động tùy theo quy mô phát triển của công ty.
LeaveRequest.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class LeaveRequest { private int days; public LeaveRequest(int days) { this.days = days; } public int getDays() { return days; } }
Approver.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public abstract class Approver { protected Approver nextApprover; public void approveLeave(LeaveRequest request) { System.out.println("Checking permission for " + this.getClass().getSimpleName()); if (this.canApprove(request.getDays())) { this.doApproving(request); } else if (nextApprover != null) { nextApprover.approveLeave(request); } } public void setNext(Approver approver) { this.nextApprover = approver; } protected abstract boolean canApprove(int numberOfDays); protected abstract void doApproving(LeaveRequest request); }
Supervisor.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class Supervisor extends Approver { @Override protected boolean canApprove(int numberOfDays) { return numberOfDays <= 3; } @Override protected void doApproving(LeaveRequest request) { System.out.println("Leave request approved for " + request.getDays() + " days by Supervisor"); } }
DeliveryManager.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class DeliveryManager extends Approver { @Override protected boolean canApprove(int numberOfDays) { return numberOfDays <= 5; } @Override protected void doApproving(LeaveRequest request) { System.out.println("Leave request approved for " + request.getDays() + " days by Delivery Manager"); } }
Director.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class Director extends Approver { @Override protected boolean canApprove(int numberOfDays) { return numberOfDays > 5; } @Override protected void doApproving(LeaveRequest request) { System.out.println("Leave request approved for " + request.getDays() + " days by Director"); } }
LeaveRequestWorkFlow.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class LeaveRequestWorkFlow { public static Approver getApprover() { Approver supervisor = new Supervisor(); Approver manager = new DeliveryManager(); Approver director = new Director(); supervisor.setNext(manager); manager.setNext(director); return supervisor; } }
App.java
package com.gpcoder.patterns.behavioral.cor.leaverequest; public class App { public static void main(String[] args) { LeaveRequestWorkFlow.getApprover().approveLeave(new LeaveRequest(2)); System.out.println("---"); LeaveRequestWorkFlow.getApprover().approveLeave(new LeaveRequest(5)); System.out.println("---"); LeaveRequestWorkFlow.getApprover().approveLeave(new LeaveRequest(7)); } }
Output của chương trình:
Checking permission for Supervisor Leave request approved for 2 days by Supervisor --- Checking permission for Supervisor Checking permission for DeliveryManager Leave request approved for 5 days by Delivery Manager --- Checking permission for Supervisor Checking permission for DeliveryManager Checking permission for Director Leave request approved for 7 days by Director
Ở ví dụ này, chain của chúng ta sẽ kết thúc khi có một đối tượng có thể xử lý.
Lợi ích của Chain of Responsibility Pattern là gì?
- Giảm kết nối (loose coupling): Thay vì một đối tượng có khả năng xử lý yêu cầu chứa tham chiếu đến tất cả các đối tượng khác, nó chỉ cần một tham chiếu đến đối tượng tiếp theo. Tránh sự liên kết trực tiếp giữa đối tượng gửi yêu cầu (sender) và các đối tượng nhận yêu cầu (receivers).
- Tăng tính linh hoạt : đảm bảo Open/Closed Principle.
- Phân chia trách nhiệm cho các đối tượng: đảm bảo Single Responsibility Principle.
- Có khả năng thay đổi dây chuyền (chain) trong thời gian chạy.
- Không đảm bảo có đối tượng xử lý yêu cầu.
Sử dụng Chain of Responsibility Pattern khi nào?
- Có nhiều hơn một đối tượng có khả thực xử lý một yêu cầu trong khi đối tượng cụ thể nào xử lý yêu cầu đó lại phụ thuộc vào ngữ cảnh sử dụng.
- Muốn gửi yêu cầu đến một trong số vài đối tượng nhưng không xác định đối tượng cụ thể nào sẽ xử lý yêu cầu đó.
- Khi cần phải thực thi các trình xử lý theo một thứ tự nhất định..
- Khi một tập hợp các đối tượng xử lý có thể thay đổi động: tập hợp các đối tượng có khả năng xử lý yêu cầu có thể không biết trước, có thể thêm bớt hay thay đổi thứ tự sau này.
Tài liệu tham khảo:
- https://sourcemaking.com/design_patterns/chain_of_responsibility
- https://refactoring.guru/design-patterns/chain-of-responsibility
- https://www.javatpoint.com/chain-of-responsibility-pattern
- https://www.tutorialspoint.com/design_pattern/chain_of_responsibility_pattern.htm
- Design Patterns: Elements of Reusable Object-Oriented Software – GOF