Кто-нибудь не согласен с утверждением: «использование switch - плохой стиль ООП»? - PullRequest
26 голосов
/ 15 февраля 2009

Я видел, как в нескольких потоках / комментариях к stackoverflow написано, что использование switch - просто плохой стиль ООП. Лично я с этим не согласен.

Во многих случаях вы не можете добавить код (то есть методы) к enum классам, которые хотите включить, потому что вы ими не управляете, возможно, они находятся в стороннем jar-файле. Будут и другие случаи, когда использование функциональности для самого перечисления является плохой идеей, поскольку она нарушает некоторые соображения разделения проблем или фактически является функцией чего-то другого , а также перечисление .

Наконец, переключатели лаконичны и понятны:

boolean investable;
switch (customer.getCategory()) {
    case SUB_PRIME:
    case MID_PRIME:
        investible = customer.getSavingsAccount().getBalance() > 1e6; break;
    case PRIME:
        investible = customer.isCeo(); break;
}

Я не защищаю каждое использование switch, и я не говорю, что всегда есть путь. Но такие утверждения, как «Переключатель - это запах кода», на мой взгляд, просто неверны. Кто-нибудь еще согласен?

Ответы [ 22 ]

56 голосов
/ 15 февраля 2009

Я думаю, что такие заявления, как

Использование оператора switch - неправильный стиль ООП.

и

Операторы case почти всегда можно заменить полиморфизмом.

упрощает. Правда в том, что операторы case, которые включают type , имеют плохой стиль ООП. Это те, которые вы хотите заменить полиморфизмом. Включение значения в порядке.

17 голосов
/ 15 февраля 2009

Принимая ваше наблюдение:

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

и один из ваших комментариев:

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

Вы правы, насколько это возможно.

boolean investable = customer.isInvestable();

не является оптимальным решением для той гибкости, о которой вы говорите. Однако в первоначальном вопросе не упоминалось о существовании отдельного базового класса Product.

Учитывая доступность дополнительной информации, наилучшим решением будет

boolean investable = product.isInvestable(customer);

Решение об инвестируемости принимается (полиморфно!) Продуктом в соответствии с аргументом «реального мира», и это также позволяет избежать необходимости создавать новые подклассы клиента при каждом добавлении продукта. Продукт может использовать любые методы, которые он хочет, чтобы сделать это определение на основе открытого интерфейса Заказчика. Я все еще спрашиваю, есть ли соответствующие дополнения, которые можно было бы сделать в интерфейсе Клиента, чтобы устранить необходимость в переключении, но это все еще может быть наименьшее из всех зол.

В конкретном примере, однако, я бы соблазнился сделать что-то вроде:

if (customer.getCategory() < PRIME) {
    investable = customer.getSavingsAccount().getBalance() > 1e6;
} else {
    investable = customer.isCeo();
}

Я считаю, что это чище и яснее, чем перечисление всех возможных категорий в переключателе, я подозреваю, что это более вероятно отражает мыслительные процессы «реального мира» («они ниже простого? mid-prime? "), и он не требует повторного посещения этого кода, если в какой-то момент добавлено обозначение SUPER_PRIME.

16 голосов
/ 15 февраля 2009

Переключатели являются запахом кода при использовании в чистом коде OO. Это не значит, что они ошибочны по определению, просто нужно подумать об их использовании. Будьте особенно осторожны.

Мое определение переключателя здесь также включает операторы if-then-else, которые можно легко переписать как операторы switch.

Переключатели могут быть признаком того, что вы не определяете поведение, близкое к данным, с которыми он работает, и не используете, например, полиморфизм подтипа.

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

Наконец, при написании ОО-программ переключатели оказываются очень полезными на «краю» вашей ОО-модели, когда что-то входит в вашу ОО-модель из внешнего мира без ОО, и вам нужно преобразовать эту внешнюю сущность в понятие ОО , Лучше всего сделать это как можно раньше. Например: int из базы данных можно преобразовать в объект с помощью переключателя.

int dbValue = ...;

switch (dbValue)
{
  case 0: return new DogBehaviour();
  case 1: return new CatBehaviour();
  ...
  default: throw new IllegalArgumentException("cannot convert into behaviour:" + dbValue);  
}

РЕДАКТИРОВАТЬ после прочтения некоторых ответов.

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

Двойная отправка: полиморфизм дважды. Но ваш класс посетителей, по сути, все еще имеет большое значение, и у него есть некоторые из тех же проблем, что описаны выше.

Кроме того, по примеру OP полиморфизм должен относиться к категории клиента, а не к Customer.

Включение значения в порядке: хорошо, но операторы switch в большинстве случаев используются для тестирования одного значения int, char, enum, ..., в отличие от if-then -также можно проверить диапазоны и более экзотические условия. Но если мы отправляем это единственное значение, и оно не находится на грани нашей ОО-модели, как объяснено выше, тогда кажется, что переключатели часто используются для диспетчеризации по типу, а не по значению. Или: если вы можете , а не заменить условную логику if-then-else переключателем, то вы, вероятно, в порядке, в противном случае, вероятно, нет. Поэтому я думаю, что переключатели в ООП - это запах кода, а утверждение

Включение типа - плохой стиль ООП, переключение значения в порядке.

само упрощено.

И вернемся к исходной точке: switch неплохо, просто не всегда ОО. Вам не нужно использовать ОО, чтобы решить вашу проблему. Если вы используете ООП, то переключатели - это то, что вам нужно уделить дополнительное внимание.

14 голосов
/ 15 февраля 2009

Это плохой стиль ООП.

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

12 голосов
/ 15 февраля 2009

Во всяком случае, я сыт по горло людьми, описывающими этот стиль программирования - при котором кучка получателей добавляется к типам «низкого зависания» (Клиент, Счет, Банк) и полезный код распространяется по системе в «контроллерах», «помощниках» и «служебных» классах - как объектно-ориентированные. Подобный код - это запах в ОО-системе, и вы должны спросить почему вместо того, чтобы обижаться.

7 голосов
/ 15 февраля 2009

Конечно, переключатели плохие OO, вы не должны помещать возврат в середину функции, магические значения плохие, ссылки никогда не должны быть нулевыми, условные выражения должны идти в {фигурных скобках}, но это рекомендации. Они не должны следовать религиозно. Сопровождаемость, рефакторируемость и понятность - все это очень важно, но все это имеет значение для фактического выполнения работы. Иногда у нас нет времени быть идеалистом по программированию.

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

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

6 голосов
/ 16 февраля 2009

Статья Роберта Мартина о Открытый закрытый принцип дает другую точку зрения:

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ (КЛАССЫ, МОДУЛИ, ФУНКЦИИ, И Т.Д.) ДОЛЖЕН БЫТЬ ОТКРЫТ ДЛЯ ПРОДЛЕНИЯ, НО ЗАКРЫТ ДЛЯ МОДИФИКАЦИЯ.

В вашем примере кода вы эффективно включаете клиента "Тип категории"

boolean investible ;
switch (customer.getCategory()) {
    case SUB_PRIME:
    case MID_PRIME:
        investible = customer.getSavingsAccount().getBalance() > 1e6; break;
    case PRIME:
        investible = customer.isCeo(); break;
}

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

Вместо других предложений, где isInvestible сделан методом для Customer, я бы сказал, что Cagtegory должен стать полноценным классом и использоваться для принятия этих решений:

boolean investible ;
CustomerCategory category = customer.getCategory();
investible = category.isInvestible(customer);

class PrimeCustomerCategory extends CustomerCategory {
    public boolean isInvestible(Customer customer) {
        return customer.isCeo();
    }
}
5 голосов
/ 15 февраля 2009

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

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

5 голосов
/ 15 февраля 2009

Я верю, что включение типа - это запах кода. Однако я разделяю ваши опасения по поводу разделения проблем в коде. Но они могут быть решены многими способами, которые позволяют вам все еще использовать полиморфизм, например шаблон посетителя или что-то подобное. Читайте о "Шаблонах проектирования" от Банды Четырех.

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

    interface Operation {
      void handlePrimeCustomer(PrimeCustomer customer);
      void  handleMidPrimeCustomer(MidPrimeCustomer customer);
      void  handleSubPrimeCustomer(SubPrimeCustomer customer);    
    };

    class InvestibleOperation : public Operation {
      void  handlePrimeCustomer(PrimeCustomer customer) {
        bool investible = customer.isCeo();
      }

      void  handleMidPrimeCustomer(MidPrimeCustomer customer) {
        handleSubPrimeCustomer(customer);
      }

      void  handleSubPrimeCustomer(SubPrimeCustomer customer) {
        bool investible = customer.getSavingsAccount().getBalance() > 1e6;    
      }
    };

    class SubPrimeCustomer : public Customer {
      void  doOperation(Operation op) {
        op.handleSubPrimeCustomer(this);
      }
    };

   class PrimeCustomer : public Customer {
      void  doOperation(Operation op) {
        op.handlePrimeCustomer(this);
      }
    };

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

3 голосов
/ 15 февраля 2009

Я рассматриваю операторы switch как более читаемую альтернативу блокам if / else.

Я считаю, что если вы можете свести свою логику к структуре, которая может быть оценена как единое целое, код, скорее всего, обеспечит уровень инкапсуляции, необходимый для ООП.

В какой-то момент должна быть написана настоящая (грязная) логика для доставки практической программы. Java и C # не являются строго ООП-языками, поскольку они наследуются от С. Если вы хотите применять строго ООП-код, вам нужно будет использовать язык, который не предоставляет идиомы, которые нарушают это мышление. Я считаю, что и Java, и C # должны быть гибкими.

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

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