Каковы преимущества цепочки ответственности перед списками классов? - PullRequest
15 голосов
/ 28 июня 2009

Недавно я обсуждал с другим программистом лучший способ рефакторинга огромного (1000 строк) метода, полного операторов if.

Код написан на Java, но я думаю, что эта проблема может возникнуть и на других языках, таких как C #.

Чтобы решить эту проблему, он предложил использовать шаблон цепочки ответственности. Он предложил иметь базовый класс «Хендлер». Затем «Handler1», «Handler2» и т. Д. Расширили бы «Handler».
Тогда у обработчиков будет метод «getSuccessor», который будет либо возвращать ноль (если он был последним в цепочке), либо следующий обработчик цепочки.
Затем функция handleRequest (Request) будет либо обрабатывать запрос, либо передавать его следующей цепочке, и, если ни одно из предыдущих решений не сработает, она будет возвращать только ноль или выбрасывать исключение.
Чтобы добавить новый обработчик в цепочку, кодировщик должен перейти к последнему элементу цепочки и сообщить ему, что появился новый элемент. Чтобы что-то сделать, он просто вызвал handleRequest для первого элемента цепочки.

Чтобы решить эту проблему, я предложил использовать другой подход.
У меня также будет базовый класс «Обработчик», с «Обработчиком1», «Обработчиком2», как и в предыдущем методе.
Однако не было бы метода "getSuccessor". Вместо этого у меня был бы класс Collection со списком обработчиков (Vector, ArrayList или что-то лучшее в этом случае).
Функция handleRequest все еще существует, но она не будет передавать вызов следующим обработчикам. Было бы просто обработать запрос или вернуть ноль.
Для обработки запроса можно использовать

for(Handler handle : handlers){
    result = handle.handleRequest(request);
    if(result!=null) return result;
}
throw new CouldNotParseRequestException(); //just like in the other approach

Или, чтобы предотвратить дублирование кода, в класс коллекции можно добавить метод parseRequest (request). Чтобы добавить новый обработчик, нужно перейти к конструктору коллекции (или к статическому блоку {}, или к чему-нибудь эквивалентному) и просто добавить код «addHandler (new Handler3 ());».

Точно, какие преимущества цепочки ответственности мне не хватает при таком подходе? Какой метод является лучшим (при условии, что является лучшим методом)? Зачем? Какие потенциальные ошибки и проблемы может вызвать каждый метод проектирования?

Для тех, кому нужен контекст, вот как выглядел оригинальный код:

if(x instanceof Type1)
{
//doSomething1
} else if(x instanceof Type2)
{
//doSomething2
}
//etc.

Ответы [ 3 ]

8 голосов
/ 29 июня 2009

Какой подход лучше, зависит от того, что хотят делать ваши обработчики.

Если обработчики могут полностью обработать запрос запроса самостоятельно, ваш подход вполне подойдет. Обработчики не имеют ссылки на другие обработчики, что делает интерфейс обработчика простым. В отличие от стандартной реализации Chain of Responsibility, вы можете добавлять или удалять обработчики из середины цепочки. На самом деле вы можете создавать различные цепочки в зависимости от типа запроса.

Одной из проблем вашего подхода является то, что обработчик не может выполнить предварительную или последующую обработку запроса. Если эта функциональность необходима, тогда лучше использовать Chain of Responsibility. В CoR обработчик является ответственным за делегирование следующему обработчику в цепочке, поэтому обработчик может выполнять предварительную обработку и / или последующую обработку, включая изменение или замену ответа от следующего обработчика в цепочке. Таким образом, CoR очень похож на Decorator; это просто намерение, которое отличается.

Поскольку в CoR обработчик сохраняет ссылку на следующий элемент в цепочке, вы не можете добавлять или удалять элементы из середины цепочки. Вариант CoR, позволяющий добавлять или удалять элементы из середины цепочки, представляет собой цепочку фильтров (см., Например, javax.servlet.FilterChain ).

Пример кода, который вы показали, представлял собой набор операторов «if», которые вели себя по-разному на основе типа объекта. Если это типично для кода, который вы очищаете, вы можете просто получить карту из типа запроса в требуемый обработчик.

Другим подходом к удалению операторов if является наследование. Если у вас было какое-то поведение, которое вам нужно было сделать, и был один вариант для веб-сервера и другой вариант для сервера SOAP, вы могли бы иметь WebServerRequestHandler и SoapServerRequestHandler, каждый из которых расширяет RequestHandler. Преимущество наследования заключается в том, что существует более ясное место для размещения логики, которая является общей для обоих типов запросов. Недостатком является то, что поскольку Java не имеет множественного наследования, вы можете моделировать только одномерные проблемы.

7 голосов
/ 28 июня 2009

Мне нравится ваша идея с коллекцией лучше, чем эти преемники. Это позволяет легко и понятно манипулировать этим набором обработчиков: интерфейс коллекций хорошо известен, и все понимают, как выполнять итерацию по списку, а что нет.

Если вы используете этот преемник, предложенный другом, постарайтесь не впасть в очень глубокую рекурсию (если ваша платформа не поддерживает хвостовые вызовы, я не знаю, способны ли JVM на это).

Я бы не рекомендовал добавлять какие-либо методы в коллекцию. Вы получаете гораздо более сложный дизайн, который труднее понять и сложнее изменить. Есть две отдельные проблемы: хранение набора обработчиков и интерпретация этих обработчиков как цепочки ответственности. Метод, который обрабатывает запросы путем итерации по коллекции, находится на более высоком уровне абстракции, чем методы обслуживания коллекции, поэтому не должен принадлежать интерфейсу коллекции.

0 голосов
/ 28 июня 2009

Не могу сказать, является ли Chain of Responsibility вашим ответом, или даже если применяется GoF. Посетитель может быть правильным. Недостаточно информации, чтобы быть уверенным.

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

Имейте в виду «делай самое простое из возможных». Не прыгайте в комплекс, пока не докажете себе, что он вам нужен.

Я недавно читал о кампании против IF , которая продвигает эту идею. Звучит вполне уместно здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...