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

Заменить условный полиморфизм - славный в теории, но не практический

"Заменить условный на полиморфизм" является изящным только тогда, когда тип объекта, который вы делаете для оператора switch/if, уже выбран для вас. В качестве примера у меня есть веб-приложение, которое считывает параметр строки запроса под названием "действие". Действие может иметь значения "просмотра", "редактирования", "сортировки" и т.д. Итак, как мне реализовать это с полиморфизмом? Ну, я могу создать абстрактный класс под названием BaseAction и получить из него ViewAction, EditAction и SortAction. Но разве мне не нужно условие, чтобы решить, какой аромат типа BaseAction нужно создать? Я не вижу, как вы можете полностью заменить условности полиморфизмом. Во всяком случае, условные выражения просто подталкиваются к вершине цепочки.

EDIT:

public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view";  // suppose user can pass either "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

switch (action)
{
    case "view":
        theAction = new ViewAction();
        break;

    case "edit":
        theAction = new EditAction();
        break;

    case "sort":
        theAction = new SortAction();
        break;
}

theAction.doSomething();    // So I don't need conditionals here, but I still need it to decide which BaseAction type to instantiate first. There no way to completely get rid of the conditionals.
4b9b3361

Ответ 1

Вы правы - "условности выталкиваются на вершину цепочки", но нет "просто" об этом. Это очень мощный. Как говорит @thkala, вы просто делаете выбор один раз; оттуда, объект знает, как заниматься своим делом. Подход, который вы описываете - BaseAction, ViewAction и остальное - это хороший способ сделать это. Попробуйте и посмотрите, насколько чище ваш код.

Когда у вас есть один метод factory, который принимает строку типа "Просмотр" и возвращает действие, и вы называете это, вы выделили свою условность. Это здорово. И вы не можете правильно оценить силу, пока вы ее не пробовали, так что сделайте снимок!

Ответ 2

Несколько вещей, которые следует учитывать:

  • Вы только создаете экземпляр каждого объекта один раз. Как только вы это сделаете, больше не потребуется никаких условностей относительно его типа.

  • Даже в разовых случаях, сколько условностей вы бы избавились, если вы использовали подклассы? Код с использованием условных условностей, подобных этому, весьма склонен к тому, что он снова и снова и снова заполняется точно таким же условным...

  • Что произойдет, когда вам понадобится значение foo Action в будущем? Сколько мест вы должны изменить?

  • Что делать, если вам нужен bar, который немного отличается от foo? С классами вы просто наследуете BarAction от FooAction, переопределяя одно, что вам нужно изменить.

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

Ответ 3

Несмотря на то, что последний ответ был год назад, я хотел бы сделать несколько обзоров/комментариев по этой теме.

Обзор отзывов

Я согласен с @CarlManaster о кодировании инструкции switch один раз, чтобы избежать всех известных проблем с дублированием кода, в данном случае связанных с условностями (некоторые из них упоминаются @thkala).

Я не считаю, что подход, предложенный @KonradSzałwiński или @AlexanderKogtenkov, подходит для этого сценария по двум причинам:

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

Обратите внимание, что эти решения позволяют это сделать (просто присваивая имя действия новому экземпляру действия), в то время как статическое решение на основе коммутатора не имеет (сопоставления жестко запрограммированы). Кроме того, вам все равно потребуется условие для проверки того, задан ли заданный ключ в таблице сопоставления, если не требуется действие (часть default оператора switch).

Во-вторых, в этом конкретном примере dictionaries являются действительно скрытыми реализациями оператора switch. Более того, было бы легче прочитать/понять оператор switch с предложением по умолчанию, чем мысленно выполнить код, возвращающий объект обработки из таблицы сопоставления, включая обработку не определенного ключа.

Есть способ избавиться от всех условных выражений, включая оператор switch:

Удаление оператора switch (без каких-либо условных условий)

Как создать правильный объект действия из имени действия?

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

Если вы уже определили полиморфную иерархию, нет смысла ссылаться на конкретный подкласс BaseAction: почему бы не попросить его вернуть правильный экземпляр, обрабатывающий действие по его имени?

Это обычно реализуется с помощью того же оператора switch, который вы написали (скажем, метода factory)... но вот что:

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.

Наконец, используйте здравый смысл и вкус, чтобы определить заранее, если конкретная техника действительно решает вашу проблему перед ее использованием. Это заманчиво, но все компромиссы (культура, старшинство разработчиков, устойчивость к изменениям, открытость и т.д.) Должны оцениваться.

Ответ 4

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

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

С другой стороны, если у вас много объектов со специализированным поведением для общего понятия, полиморфизм именно то, что вы хотите. Например, в игре может быть много объектов, которые игрок может "активировать", но каждый реагирует по-разному. Вы можете реализовать это со сложными условиями (или, скорее, оператором switch), но полиморфизм будет лучше. Полиморфизм позволяет вам вводить новые объекты и поведения, которые не были частью вашего первоначального дизайна (но вписывались в его дух).

В вашем примере, было бы хорошей идеей абстрагироваться над объектами, которые поддерживают действия view/edit/sort, но, возможно, не абстрактно эти действия сами. Здесь тест: вы когда-нибудь захотите вложить эти действия в коллекцию? Вероятно, нет, но у вас может быть список объектов, которые их поддерживают.

Ответ 5

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

Таблица поиска может использоваться для сопоставления входных строк с требуемыми объектами:

action = table.lookup (action_name); // Retrieve an action by its name
if (action == null) ...              // No matching action is found

Код инициализации позаботится о создании необходимых объектов, например

table ["edit"] = new EditAction ();
table ["view"] = new ViewAction ();
...

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

Ответ 6

public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view"; // suppose user can pass either
                        // "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

switch (action)
{
    case "view":
        theAction = new ViewAction();
        break;

    case "edit":
        theAction = new EditAction();
        break;

    case "sort":
        theAction = new SortAction();
        break;
}

theAction.doSomething();

Поэтому мне здесь не нужны условные обозначения, но мне все же нужно решить, какой тип BaseAction нужно создать в первую очередь. Невозможно полностью избавиться от условностей.

Ответ 7

Я думал об этой проблеме, вероятно, больше, чем остальные разработчики, с которыми я встречался. Большинство из них полностью не осознают затраты на поддержание длинных вложенных операторов if-else или коммутаторов. Я полностью понимаю вашу проблему при применении решения "Заменить условное с полиморфизмом" в вашем случае. Вы успешно заметили, что полиморфизм работает, пока объект уже выбран. В этом протекте также сказано, что эту проблему можно свести к ассоциации [key] → [class]. Вот, например, реализация AS3 решения.

private var _mapping:Dictionary;
private function map():void
{
   _mapping["view"] = new ViewAction();
   _mapping["edit"] = new EditAction();
   _mapping["sort"] = new SortAction();
}

private function getAction(key:String):BaseAction
{
    return _mapping[key] as BaseAction;
} 

Запуск, который вам понравится:

public function run(action:String):void
{
   var selectedAction:BaseAction = _mapping[action];
   selectedAction.apply();
}

В ActionScript3 существует глобальная функция, называемая getDefinitionByName (ключ: String): Class. Идея состоит в том, чтобы использовать ваши ключевые значения в соответствии с именами классов, которые представляют собой решение вашего условия. В вашем случае вам нужно будет изменить "вид" на "ViewAction", "edit" на "EditAction" и "sort" на "SortAtion". Нет необходимости запоминать что-либо, используя таблицы поиска. Запуск функции будет выглядеть следующим образом:

public function run(action:Script):void
{
   var class:Class = getDefintionByName(action);
   var selectedAction:BaseAction = new class();
   selectedAction.apply();
}

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

Пожалуйста, оставьте комментарий, даже если вы не согласны со мной.

Ответ 8

Полиморфизм - это метод связывания. Это особый случай, известный как "Объектная модель". Объектные модели используются для управления сложными системами, такими как схема или чертеж. Рассмотрим что-то сохраненное/упорядоченное текстовое форматирование: элемент "А", подключенный к элементам "В" и "С". Теперь вам нужно знать, что связано с A. Парень может сказать, что я не собираюсь создавать объектную модель для этого, потому что могу считать это во время разбора, однопроходного. В этом случае вы можете быть правы, вы можете уйти без объектной модели. Но что, если вам нужно сделать много сложных манипуляций с импортным дизайном? Будете ли вы манипулировать им в текстовом формате или отправлять сообщения, вызывая java-методы и ссылаясь на java-объекты, более удобно? Вот почему было упомянуто, что вам нужно сделать перевод только один раз.

Ответ 9

Вы можете сохранить строку и соответствующий тип действия где-нибудь в хэш-карте.

public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view"; // suppose user can pass either
                        // "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

theAction = actionMap.get(action); // decide at runtime, no conditions
theAction.doSomething();