Chain of Responsibility
Проблем
Да се разработи система за обработка на вътрешни заявки.
В системата могат да постъпват заявки от различен тип: счетоводни, административни и технически. Всяка заявка трябва да бъде обработена от подходящ отдел.
Решение без използване на шаблона
// defined in advance class Request with public enum for request type
public class RequestProcessor {
public String processRequest(Request request) {
if (Request.RequestType.ACCOUNTING == request.getRequestType()) {
return "Request has been processed by accounting department";
}
if (Request.RequestType.ADMINISTRATIVE == request.getRequestType()) {
return "Request has been processed by administrative department";
}
if (Request.RequestType.TECHNICAL == request.getRequestType()) {
return "Request has been processed by technical department";
}
return "No department to process request found!";
}
}
Недостатъци на решението
При този подход класът RequestProcessor съдържа логиката за всички възможни типове заявки. При добавяне на нов тип заявка методът processRequest() трябва да бъде променян.
Това увеличава условната логика, затруднява разширяването на системата и обвързва обработката на всички заявки в един клас.
Следователно е необходимо решение, при което отделните обработващи звена могат да бъдат разделени в самостоятелни класове и подредени във верига.
Шаблонът като решение
Chain of Responsibility организира обработващите обекти във верига. Всеки обработващ обект проверява дали може да обработи заявката. Ако не може, я предава на следващия обект във веригата.
По този начин клиентският код не знае кой конкретен обект ще обработи заявката.
Дефиниция
Chain of Responsibility е поведенчески шаблон за проектиране, който предава заявка последователно през верига от обработващи обекти, докато някой от тях я обработи или веригата приключи.
Основните участници са:
- Handler - Интерфейс или абстрактен клас, който дефинира обработката на заявката и връзката към следващото звено.
- Concrete Handler - Конкретно звено от веригата, което обработва определен тип заявки.
- Client - Създава веригата и подава заявката към първото звено.
UML диаграма
Примерна реализация
Заявка
public class Request {
public enum RequestType {
ADMINISTRATIVE,
ACCOUNTING,
TECHNICAL
}
private String name;
private RequestType requestType;
public Request(String name, RequestType requestType) {
this.name = name;
this.requestType = requestType;
}
public RequestType getRequestType() {
return requestType;
}
}
Handler
public interface RequestHandler {
void setNextHandler(RequestHandler handler);
String processRequest(Request request);
}
Concrete Handlers
public class AccountingDepartment implements RequestHandler {
private RequestHandler nextHandler;
@Override
public void setNextHandler(RequestHandler requestHandler) {
this.nextHandler = requestHandler;
}
@Override
public String processRequest(Request request) {
if (Request.RequestType.ACCOUNTING == request.getRequestType()) {
return "Request has been processed by accounting department";
} else if (Objects.nonNull(nextHandler)) {
return nextHandler.processRequest(request);
} else {
return "No department to process request found!";
}
}
}
public class AdministrativeDepartment implements RequestHandler {
private RequestHandler nextHandler;
@Override
public void setNextHandler(RequestHandler requestHandler) {
this.nextHandler = requestHandler;
}
@Override
public String processRequest(Request request) {
if (Request.RequestType.ADMINISTRATIVE == request.getRequestType()) {
return "Request has been processed by administrative department";
} else if (Objects.nonNull(nextHandler)) {
return nextHandler.processRequest(request);
} else {
return "No department to process request found!";
}
}
}
public class TechnicalDepartment implements RequestHandler {
private RequestHandler nextHandler;
@Override
public void setNextHandler(RequestHandler requestHandler) {
this.nextHandler = requestHandler;
}
@Override
public String processRequest(Request request) {
if (Request.RequestType.TECHNICAL == request.getRequestType()) {
return "Request has been processed by technical department";
} else if (Objects.nonNull(nextHandler)) {
return nextHandler.processRequest(request);
} else {
return "No department to process request found!";
}
}
}
Създаване на веригата
public class RequestProcessor {
private final RequestHandler firstHandler;
public RequestProcessor() {
RequestHandler accounting = new AccountingDepartment();
RequestHandler administrative = new AdministrativeDepartment();
RequestHandler technical = new TechnicalDepartment();
accounting.setNextHandler(administrative);
administrative.setNextHandler(technical);
firstHandler = accounting;
}
public String processRequest(Request request) {
return firstHandler.processRequest(request);
}
}
Използване
public class Application {
public static void main(String[] args) {
RequestProcessor processor = new RequestProcessor();
System.out.println(processor.processRequest(new Request("Monthly payment",
Request.RequestType.ACCOUNTING)));
System.out.println(processor.processRequest(new Request("New workstation",
Request.RequestType.TECHNICAL)));
}
}
Интерфейсът RequestHandler дефинира общото поведение на всички звена във веригата. Освен метода за обработка на заявката той съдържа и метод за определяне на следващия обработващ обект.
Всеки отдел реализира интерфейса RequestHandler и проверява дали може да обработи постъпилата заявка. Ако може, обработката приключва. В противен случай заявката се предава към следващото звено чрез метода processRequest().
Класът RequestProcessor създава веригата от обработващи обекти и съхранява референция само към първото звено. Клиентският код винаги подава заявката към началото на веригата, без да знае кой конкретен обект ще я обработи.
[!IMPORTANT] Всеки обработващ обект взема самостоятелно решение дали да обработи заявката или да я предаде към следващото звено. Клиентът не знае кой обект ще извърши обработката и не е свързан директно с конкретните обработващи класове.
Предимства
- намалява зависимостите между клиента и обработващите обекти;
- позволява динамично изграждане и промяна на веригата;
- улеснява добавянето на нови обработващи обекти;
- подпомага спазването на принципа Open/Closed;
- разпределя отговорността между множество класове.
Недостатъци
- обработката може да премине през голяма част от веригата;
- при неправилно изградена верига заявката може да остане необработена;
- голям брой обработващи обекти могат да усложнят архитектурата.
Приложение
Chain of Responsibility е подходящ когато:
- няколко обекта могат да обработят една и съща заявка;
- предварително не е известно кой ще обработи заявката;
- обработващите обекти трябва лесно да се добавят, премахват или пренареждат;
- се цели намаляване на зависимостите между клиента и конкретните обработващи класове.