Хотя последний ответ был год назад, я хотел бы сделать несколько отзывов / комментариев по этой теме.
Ответы на обзор
Я согласен с @CarlManaster относительно однократного кодирования оператора switch
, чтобы избежать всех хорошо известных проблем работы с дублированным кодом, в данном случае с использованием условий (некоторые из них упомянуты @thkala).
Я не верю, что подход, предложенный @ KonradSzałwiński или @AlexanderKogtenkov, подходит к этому сценарию по двум причинам:
Во-первых, из описанной проблемы вам не нужно динамически изменять отображение между именем действия и экземпляром действия, которое его обрабатывает.
Обратите внимание, что эти решения позволяют делать это (просто назначая имя действия новому экземпляру действия), в то время как статическое решение на основе коммутатора этого не делает (отображения жестко закодированы).
Кроме того, вам все равно понадобится условие, чтобы проверить, определен ли данный ключ в таблице сопоставления, если не нужно предпринимать действия (часть default
оператора switch).
Во-вторых, в этом конкретном примере dictionaries
- это действительно скрытые реализации оператора switch. Более того, может быть проще прочитать / понять инструкцию switch с предложением по умолчанию, чем мысленно выполнять код, который возвращает объект обработки из таблицы отображения, включая обработку не определенного ключа.
Есть способ избавиться от всех условных выражений, включая оператор switch:
Удаление оператора switch (вообще не использовать условия)
Как создать правильный объект действия из имени действия?
Я буду не зависимым от языка, поэтому этот ответ не получит такой длины , но хитрость заключается в том, чтобы понять, что классы тоже являются объектами .
Если вы уже определили полиморфную иерархию, нет смысла ссылаться на конкретный подкласс BaseAction
: почему бы не попросить его вернуть правильный экземпляр, обрабатывающий действие по его имени?
Это обычно реализуется тем же оператором switch, который вы написали (скажем, фабричным методом) ... но как насчет этого:
public class BaseAction {
//I'm using this notation to write a class method
public static handlingByName(anActionName) {
subclasses = this.concreteSubclasses()
handlingClass = subclasses.detect(x => x.handlesByName(anActionName));
return new handlingClass();
}
}
Итак, что делает этот метод?
Сначала извлекаются все конкретные подклассы этого (что указывает на BaseAction
). В вашем примере вы получите коллекцию с ViewAction
, EditAction
и SortAction
.
Обратите внимание, что я сказал конкретные подклассы, а не все подклассы. Если иерархия глубже, то конкретные подклассы всегда будут теми, которые находятся внизу иерархии (лист). Это потому, что они единственные, кто не должен быть абстрактным и обеспечивать реальную реализацию.
Во-вторых, получите первый подкласс, который отвечает, может ли он обрабатывать действие по его имени (я использую лямбда / закрытые нотации). Пример реализации метода класса handlesByName
для ViewAction
будет выглядеть следующим образом:
public static class ViewAction {
public static bool handlesByName(anActionName) {
return anActionName == 'view'
}
}
В-третьих, мы отправляем сообщение новому классу, который обрабатывает действие, эффективно создавая его экземпляр.
Конечно, вам приходится иметь дело со случаем, когда ни один из подклассов не обрабатывает действие по его имени. Многие языки программирования, включая Smalltalk и Ruby, позволяют передавать методу обнаружения второй лямбда / замыкание, которое будет оцениваться только в том случае, если ни один из подклассов не соответствует критериям.
Кроме того, вам придется иметь дело со случаем, когда более одного подкласса обрабатывает действие по его имени (возможно, один из этих методов был закодирован неверно).
Заключение
Одним из преимуществ этого подхода является то, что новые действия могут поддерживаться путем написания (а не изменения) существующего кода: просто создайте новый подкласс BaseAction
и правильно реализуйте метод класса handlesByName
. Он эффективно поддерживает добавление новой функции, добавляя новую концепцию, не изменяя существующую имплементацию. Ясно, что если новая функция требует добавления нового полиморфного метода в иерархию, потребуются изменения.
Кроме того, вы можете предоставить разработчикам, используя обратную связь вашей системы: «Предоставленное действие не обрабатывается никаким подклассом BaseAction, пожалуйста, создайте новый подкласс и реализуйте абстрактные методы». Для меня тот факт, что сама модель говорит вам, что не так (вместо того, чтобы пытаться мысленно выполнить поисковую таблицу), добавляет ценность и дает четкие указания о том, что должно быть сделано.
Да, это может звучать чрезмерно. Пожалуйста, будьте непредвзяты и осознайте, что решение о том, является ли решение чрезмерным или нет, должно зависеть, помимо прочего, от культуры разработки конкретного языка программирования, который вы используете. Например, парни .NET, вероятно, не будут использовать его, потому что .NET не позволяет вам рассматривать классы как реальные объекты, в то время как, с другой стороны, это решение используется в культурах Smalltalk / Ruby.
Наконец, используйте здравый смысл и вкус, чтобы заранее определить, действительно ли конкретный метод решает вашу проблему, прежде чем использовать его. Это заманчиво, но все компромиссы (культура, стаж работы разработчиков, сопротивление изменениям, открытость и т. Д.) Должны быть оценены.