Пример: служба перевода (локальный->глобальный->Babelfish).
О Цепочке откликов(Chain of responsibility) можно думать как об обобщенном рекурсивном использовании объектов Strategy. Вы можете сделать вызов, а каждая Strategy в связанной последовательности пробует удовлетворить вызов. Процесс заканчивается, когда одна из стратегий завершается удачно или когда заканчивается цепочка. В рекурсии один метод вызывает себя снова и снова, пока не будет достигнута цель. В Chain of responsibility метод вызывает себя, что приводит к вызову (при перемещении вниз по цепочки Strategy) другой реализации метода и т. д., пока процесс не достигнет конца. Процесс окончания определяется либо завершением цепочки (в этом случае возвращается объект по умолчанию; вы способны или не способны предоставить результат по умолчанию, так что вы должны быть способны определить успешность или провал цепочки), либо успешной отработкой одной из Strategy.
Вместо вызова единственного метода для удовлетворения запроса множественные методы цепочки имеют больше шансов удовлетворить запрос, так что такое поведение характерно для экспертных систем. Так как цепочка в действительности является связанным списком, она может создаваться динамически, так что вы можете также думать, о ней, как о более общем случае переключателя (switch), который строится динамически.
В GoF есть большое количество рассуждений о том, как должна создаваться цепочка откликов в виде связанного списка. Однако, когда вы взглянете на шаблон, то увидите, что на самом деле это не имеет значения, как содержать цепочку. Это детали реализации. Так как GoF была написана до встраивания Стандартной Библиотеки Шаблонов (STL) в большинство компиляторов C++, то объяснить причины этого объяснения достаточно легко: (1) в то время не было списков и (2) приходилось создавать списки и структуры данных, которые часто изучают в академиях, а идея того, что структура данных должна быть стандартным инструментом, доступным в языке программирования, не возникла у авторов GoF. Я решил, что реализация Chain of responsibility в виде цепочки (реально, как связанный список) ничего не прибавит к решению, а может быть упрощена при использовании стандартного класса Java List, как показано ниже. Более того, вы увидите, что я сделал некоторое усилие для разделения части реализации поддержки цепочки от вариантов Strategy, так что этот код может быть легко использован повторно.
В приведенном выше примере StrategyPattern.java мы пробовали автоматически найти решение. Chain of responsibility предоставляет способ сделать это, объединив объекты Strategy вместе и предоставив им механизм для автоматической рекурсии через каждое звено цепи:
//: chainofresponsibility:FindMinima.java
package chainofresponsibility;
import com.bruceeckel.util.*; // Arrays2.toString()
import junit.framework.*;
// Переносит результирующие данные и
// проверяет удалась ли стратегия:
class LineData {
public double[] data;
public LineData(double[] data) {
this.data = data;
}
private boolean succeeded;
public boolean isSuccessful() {
return succeeded;
}
public void setSuccessful(boolean b) {
succeeded = b;
}
}
interface Strategy {
LineData strategy(LineData m);
}
class LeastSquares implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying LeastSquares algorithm");
LineData ld = (LineData) m;
// [ реальная проверка/вычисления здесь ]
LineData r = new LineData(new double[] { 1.1, 2.2 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class NewtonsMethod implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying NewtonsMethod algorithm");
LineData ld = (LineData) m;
// [ реальная проверка/вычисления здесь ]
LineData r = new LineData(new double[] { 3.3, 4.4 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class Bisection implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying Bisection algorithm");
LineData ld = (LineData) m;
// [ реальная проверка/вычисления здесь ]
LineData r = new LineData(new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
class ConjugateGradient implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying ConjugateGradient algorithm");
LineData ld = (LineData) m;
// [ реальная проверка/вычисления здесь ]
LineData r = new LineData(new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
class MinimaFinder {
private static Strategy[] solutions = { new LeastSquares(),
new NewtonsMethod(), new Bisection(), new ConjugateGradient(), };
public static LineData solve(LineData line) {
LineData r = line;
for (int i = 0; i < solutions.length; i++) {
r = solutions[i].strategy(r);
if (r.isSuccessful())
return r;
}
throw new RuntimeException("unsolved: " + line);
}
}
public class FindMinima extends TestCase {
LineData line = new LineData(new double[] { 1.0, 2.0, 1.0, 2.0, -1.0, 3.0,
4.0, 5.0, 4.0 });
public void test() {
System.out.println(Arrays2.toString(((LineData) MinimaFinder
.solve(line)).data));
}
public static void main(String args[]) {
junit.textui.TestRunner.run(FindMinima.class);
}
} // /:~
Упражнения
- Реализуйте Chain of responsibility для создания "экспертной системы", которая решает проблему путем последовательных проб решений одного за другим, пока одно из них не подойдет. Вы должны быть способны динамически добавлять решения в экспертную систему. Тест решения должен проходить простым сравнением строки, но когда решение подходит, экспертная система должна возвращать соответствующий тип объекта ProblemSolver. Какие еще шаблоны понадобятся вам в этом случае?
- Реализуйте Chain of responsibility для создания переводчика, который начинает поиск для локальной специализированной системы перевода (которая может знать о специфике области вашей проблемы), затем переходит к более глобальной обобщенной системе и, наконец, переходит в систему BableFish, если предыдущие системы не смогли все перевести. Обратите внимание, что каждое звено цепи может частично перевести то, что ей знакомо.
- Реализуйте Chain of responsibility для создания инструмента, помогающего переформатировать исходный код Java, испытывая различные подходы к расстановке переводов строки. Обратите внимание, что с обычным кодом и комментариями необходимо обходится по-разному, возможно придется ввести Дерево Откликов. Также обратите внимание на сходство между этим подходом и шаблоном проектирования Composite. Возможно, более общим описанием этой техники является Composite of Strategies.
← | Команда (Command): выбор операции во время выполнения | Напоминание (Memento) | → |