Цепочка обязанностей – это один из паттернов поведения.
Паттерн проектирования “Цепочка обязанностей”
Используется паттерн цепочки обязанностей для достижения слабой связанности в проектировании программного обеспечения, где запрос от клиента передается цепочке объектов для их обработки. Затем объект в цепочке сам решает, кто будет обрабатывать запрос, и нужно ли отправлять запрос следующему объекту в цепочке или нет.
Пример паттерна “Цепочка обязанностей” в JDK
Давайте посмотрим на пример цепочки обязанностей в JDK, а затем мы перейдем к реализации реального примера этого шаблона. Мы знаем, что в коде блока try-catch может быть несколько блоков catch. Здесь каждый блок catch является своего рода обработчиком для обработки определенного исключения. Таким образом, когда происходит исключение в блоке try, оно отправляется на обработку первому блоку catch. Если блок catch не в состоянии его обработать, он перенаправляет запрос следующему объекту в цепочке, то есть следующему блоку catch. Если даже последний блок catch не в состоянии его обработать, исключение выбрасывается за пределы цепочки в вызывающую программу.
Пример шаблона проектирования Цепочка обязанностей
Одним из отличных примеров шаблона Цепочка Обязанностей является банкомат для выдачи наличных. Пользователь вводит сумму для выдачи, а машина выдаёт сумму в виде определенных купюр, таких как 50$, 20$, 10$ и т. д. Если пользователь вводит сумму, которая не является кратной 10, возникает ошибка. Мы будем использовать шаблон Цепочка Обязанностей для реализации этого решения. Цепочка будет обрабатывать запрос в том же порядке, что и на рисунке ниже. Обратите внимание, что мы можем легко реализовать это решение в одной программе, но при этом сложность увеличится, и решение будет тесно связанным. Поэтому мы создадим цепь систем для выдачи купюр по 50$, 20$ и 10$.
Шаблон проектирования “Цепочка Обязанностей” – Базовые классы и интерфейс
Мы можем создать класс Currency
, который будет хранить сумму для выдачи и использоваться реализациями цепочки. Currency.java
package com.journaldev.design.chainofresponsibility;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
Базовый интерфейс должен иметь метод для определения следующего обработчика в цепочке и метод для обработки запроса. Наш интерфейс банкомата для выдачи будет выглядеть так: DispenseChain.java
package com.journaldev.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
Шаблон цепочки обязанностей – Реализация цепочки
Нам нужно создать различные классы процессоров, которые будут реализовывать интерфейс DispenseChain
и предоставлять реализацию методов выдачи. Поскольку мы разрабатываем нашу систему для работы с тремя типами купюр – 50$, 20$ и 10$, мы создадим три конкретных реализации. Dollar50Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar50Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 50){
int num = cur.getAmount()/50;
int remainder = cur.getAmount() % 50;
System.out.println("Dispensing "+num+" 50$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Dollar20Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar20Dispenser implements DispenseChain{
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 20){
int num = cur.getAmount()/20;
int remainder = cur.getAmount() % 20;
System.out.println("Dispensing "+num+" 20$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Dollar10Dispenser.java
package com.journaldev.design.chainofresponsibility;
public class Dollar10Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 10){
int num = cur.getAmount()/10;
int remainder = cur.getAmount() % 10;
System.out.println("Dispensing "+num+" 10$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
Важно отметить здесь реализацию метода выдачи. Вы заметите, что каждая реализация пытается обработать запрос и в зависимости от суммы может обработать часть или полностью. Если одна из цепочек не может полностью обработать ее, она отправляет запрос следующему процессору в цепочке для обработки оставшегося запроса. Если процессор не может обработать ничего, он просто передает тот же запрос следующей цепочке.
Шаблон проектирования цепочки обязанностей – Создание цепочки
Важный этап, и мы должны тщательно создавать цепочку, иначе процессор может не получать никаких запросов вообще. Например, в нашей реализации, если мы установим первую цепь процессоров как Dollar10Dispenser
, а затем Dollar20Dispenser
, запрос никогда не будет передан второму процессору, и цепочка станет бесполезной. Вот наша реализация банкоматного диспенсера для обработки запрошенной суммы пользователя. ATMDispenseChain.java
package com.journaldev.design.chainofresponsibility;
import java.util.Scanner;
public class ATMDispenseChain {
private DispenseChain c1;
public ATMDispenseChain() {
// инициализация цепочки
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// установка цепочки ответственности
c1.setNextChain(c2);
c2.setNextChain(c3);
}
public static void main(String[] args) {
ATMDispenseChain atmDispenser = new ATMDispenseChain();
while (true) {
int amount = 0;
System.out.println("Enter amount to dispense");
Scanner input = new Scanner(System.in);
amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("Amount should be in multiple of 10s.");
return;
}
// обработка запроса
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
Когда мы запускаем вышеуказанное приложение, мы получаем вывод, подобный следующему.
Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.
Диаграмма классов паттерна проектирования “Цепочка обязанностей”
Наш пример выдачи банкнот в банкомате с реализацией паттерна проектирования “Цепочка обязанностей” выглядит как изображено на следующем рисунке.
Важные моменты паттерна проектирования “Цепочка обязанностей”
- Клиент не знает, какая часть цепочки будет обрабатывать запрос, и отправит запрос первому объекту в цепочке. Например, в нашей программе ATMDispenseChain неизвестно, кто обрабатывает запрос на выдачу введенной суммы.
- Каждый объект в цепочке будет иметь свою собственную реализацию для обработки запроса, либо полностью, либо частично, либо для отправки его следующему объекту в цепочке.
- У каждого объекта в цепочке должна быть ссылка на следующий объект в цепочке для передачи запроса, что достигается с помощью композиции на Java.
- Тщательное создание цепочки очень важно, иначе может возникнуть ситуация, когда запрос никогда не будет перенаправлен на конкретный процессор, или в цепочке нет объектов, способных обработать запрос. В моей реализации я добавил проверку для введенной суммы пользователя, чтобы убедиться, что она полностью обрабатывается всеми процессорами, но мы можем не проверять это и генерировать исключение, если запрос достигнет последнего объекта, и в цепочке нет дополнительных объектов для передачи запроса.
- Шаблон проектирования “Цепочка обязанностей” хорошо подходит для достижения слабой связанности, но это сопряжено с тем, что требуется много классов реализации и проблемами с обслуживанием, если большая часть кода общая для всех реализаций.
Примеры шаблона Chain of Responsibility в JDK
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
Вот и всё, что касается шаблона проектирования Chain of Responsibility. Надеюсь, вам понравилось, и это помогло вам лучше понять этот шаблон проектирования.
Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java