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

Кто-то не согласен с утверждением: "Использовать переключатель - это плохой стиль ООП"?

Я видел, что это написано в нескольких потоках/комментариях в 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, и я не говорю, что это всегда путь. Но заявления, подобные "Switch - запах кода", на мой взгляд, просто неправильны. Кто-нибудь еще согласен?

4b9b3361

Ответ 1

Принимая меры:

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

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

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

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

boolean investable = customer.isInvestable();

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

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

boolean investable = product.isInvestable(customer);

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

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

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

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

Ответ 2

Я думаю, что выражения вроде

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

и

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

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

Ответ 3

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

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

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

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

Наконец, при написании программ OO переключатели очень удобны на "краю" вашей модели OO, когда что-то входит в вашу модель OO из внешнего мира вне OO, и вам нужно преобразовать этот внешний объект в понятие OO, Вам лучше всего сделать это как можно раньше. Например: 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 - там, где могут быть проверены диапазоны и более экзотические условия. Но если мы отправим это единственное значение и оно не находится на краю нашей модели OO, как объяснялось выше, то, похоже, коммутаторы часто используются для отправки по типу, а не по значению. Или: если вы не можете заменить условную логику if-then-else коммутатором, то вы, вероятно, все в порядке, иначе вы, вероятно, нет. Поэтому я думаю, что переключатели в ООП являются запахами кода, а оператор

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

сам упрощается.

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

Ответ 4

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

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

Ответ 5

Во всяком случае, мне надоели люди, описывающие этот стиль программирования, в который добавляется кучка геттеров к "низким висячим" типам (Customer, Account, Bank), и полезный код распыляется вокруг системы в классах "контроллеры", "помощники" и "полезность" - как ориентированные на объекты. Код, подобный этому, является запахом в системе OO, и вы должны спрашивать, почему вместо того, чтобы обижаться.

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

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

    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);
      }
    };

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

Ответ 9

В статье Роберта Мартина о Открытом закрытом принципе представлена ​​другая точка зрения:

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

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

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();
    }
}

Ответ 10

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

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

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

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

Ответ 11

Работа с вашими библиотеками также является запахом кода. У вас может не быть выбора, но это не делает его хорошей практикой.

Ответ 12

Я не вижу ничего плохого в использовании оператора switch в OO-коде. Моя единственная критика в том, что я сделал бы новый метод для клиента под названием IsInvestible, который скрывал эту логику. 0 неверно использовать оператор switch как внутреннюю реализацию этого метода. Как вы сказали, вы не можете добавлять методы для перечисления, но вы можете добавить дополнительные методы для Клиента.

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

Ответ 13

О знаю, откуда вы. Некоторые языки заставляют вас это делать.

String str = getStr();
switch(str) {
case "POST" : this.doPost(); break;
case "GET" : this.doGet(); break;
//and the other http instructions
}

А теперь что? Конечно, есть хороший способ ООП:

str.request(this);

Слишком плохо, что String нельзя продлить, и теперь вы планируете написать класс HttpInstruction с 8 подклассами для каждого HttpInstruction. Честно говоря, особенно когда речь идет о парсерах, это просто существенно сложно.

Это плохой ООП, это точно, но хороший код не всегда... возможен.

Позвольте мне разойтись на мгновение. Я пишу диссертацию. Мне лично не нравится обычная настройка рекурсивных функций. Обычно у вас есть funcRec (arg1, arg) и func (arg1): = func (funcRec (arg1,0));

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

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

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

Итак, скорее всего, вы застряли в Java, что затрудняет появление хорошей замены для коммутаторов во всех ситуациях. У самого было бы элегантное решение. Но вы не используете "Я", и если бы вы были, ваши коллеги не могли бы его прочитать, поэтому забудьте об этом.

И теперь найдите компромисс.

Это печально, я знаю.

Ответ 14

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

В методе factory, например, это может быть очень простая альтернатива написанию сложного и потенциально ошибочного кода на основе отражения.

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

Ответ 15

Данные, поступающие из внешних источников, по существу не могут быть объектно ориентированными, поскольку вы не вводите код. Если он содержит случаи, вы будете иметь случаи. Период.

Кроме того, ООП не является серебряной пулей. Иногда это ответ, иногда это не так.

Ответ 16

Да, я сыт по горло людьми, которые говорят вам, что это плохой стиль.

Изменить: это имело смысл, прежде чем вопрос был исправлен.

Ответ 17

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

public class NormalCustomer extends Customer {
    public boolean isInvestible() {
        return getSavingsAccount().getBalance() > 1e6;
    }
}

public class PreferredCustomer extends Customer {
    public boolean isInvestible() {
        return isCeo();
    }
}

Этот подход упростит код клиента. Клиентский код не должен знать подробностей о том, как рассчитывается "инвестиция", и ему больше не нужно нарушать "Закон Деметры" копание в состоянии объекта Customer.

Ответ 18

А теперь что? Конечно, есть хорошая OOP:

str.request(это);

Слишком плохо, что строка не может быть расширена и теперь вы планируете написать Класс HttpInstruction с 8 подклассы для каждого HttpInstruction. Честно говоря, особенно при разговоре о парсерах, это просто существенно затруднено.

Попробуйте методы расширения С#? Строка может быть расширена.

Ответ 19

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

Много кода, требующего рефакторинга в моей кодовой базе, будет иметь целые классы, пронизанные множеством операторов switch, так что вам нужно было знать о существовании каждого оператора switch.

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

Из-за ограничений по времени мы не приняли его дальше, потому что это служило нашим потребностям. Был еще большой гигантский оператор switch, но он был только один, поэтому добавление дополнительных стратегий требовало только ipmlementing интерфейса и добавления шага создания в оператор главного коммутатора.

Ответ 20

Во-первых, ваша цель не должна заключаться в достижении "хорошего стиля OO", но хорошего кода. И "хороший" означает, по крайней мере, правильное, ясное, понятное и максимально простое.

Итак, я переформулирую quesion на: "Использует ли переключатель знак плохого кода?" , потому что это действительно то, о чем я забочусь. Теперь я приступаю к ответу.

Hm, это хороший вопрос:) Обычно использование одного коммутатора не является признаком плохого кода. Но если вы включите одно и то же в нескольких точках своего класса, хорошо подумать об альтернативном дизайне, в котором вы представляете альтернативы коммутатора с дочерними классами, - и если вы считаете это, спросите себя, особенно если классы, созданные таким образом, будут специализация текущего класса и будет иметь отношение is-a. Если это так, это дает больше преимуществ использованию наследования.

Один последний комментарий: "Использование [language feature X] вообще плохо" "опасно близко к". Разработчики языка были глупы, чтобы включить в него [функцию языка X].

Ответ 21

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

А также

boolean investable = customer.isInvestable();

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

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

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

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

Ответ 22

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

Это приходит в голову:

interface Investable
{
    boolean isIvestible(Customer c);
}

class FooInvestible 
    implements Investible
{
    public boolean isInvestible(final Customer c)
    {
        // whatever logic, be it switch or other things
    }
}

"Проблема" с первоначальным использованием swtich и добавлением новых типов решений заключается в том, что вы, вероятно, закончите с огромным гнездом крыс кода, который невозможно поддерживать разумным способом. Разделение решений на классы вынуждает разделять принятие решений. Затем, даже если вы используете переключатель, код, скорее всего, останется более прочным и поддерживаемым.