Подтвердить что ты не робот

Каковы преимущества цепочки ответственности против списков классов?

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

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

Удерживайте "самое простое". Не прыгайте в комплекс, пока не убедитесь, что вам это нужно.

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