Цепочка обязанностей: шаблон проектирования в Java

Цепочка обязанностей – это один из паттернов поведения.

Паттерн проектирования “Цепочка обязанностей”

Используется паттерн цепочки обязанностей для достижения слабой связанности в проектировании программного обеспечения, где запрос от клиента передается цепочке объектов для их обработки. Затем объект в цепочке сам решает, кто будет обрабатывать запрос, и нужно ли отправлять запрос следующему объекту в цепочке или нет.

Пример паттерна “Цепочка обязанностей” в 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