Полиморфизм или условные выражения способствуют лучшему дизайну?

Указатель NULL - это тот, который указывает на никуда. Когда вы разыскиваете указатель p, вы говорите «дайте мне данные в месте, хранящемся в« p ». Когда p является нулевым указателем, местоположение, хранящееся в p, является nowhere, вы говорите «Дайте мне данные в месте« нигде ». Очевидно, он не может этого сделать, поэтому он выбрасывает NULL pointer exception.

В общем, это потому, что что-то не было правильно инициализировано.

38
задан Nik Reiman 30 September 2010 в 13:23
поделиться

11 ответов

На самом деле это делает тестирование и код легче записать.

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

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

class Animal
{
    public:
       Noise warningNoise();
       Noise pleasureNoise();
    private:
       AnimalType type;
};

Noise Animal::warningNoise()
{
    switch(type)
    {
        case Cat: return Hiss;
        case Dog: return Bark;
    }
}
Noise Animal::pleasureNoise()
{
    switch(type)
    {
        case Cat: return Purr;
        case Dog: return Bark;
    }
}

В этом простом случае каждое новое животное причины требует, чтобы были обновлены оба оператора переключения.
Вы забываете тот? Каково значение по умолчанию? СТУЧИТЕ!!

Используя полиморфизм

class Animal
{
    public:
       virtual Noise warningNoise() = 0;
       virtual Noise pleasureNoise() = 0;
};

class Cat: public Animal
{
   // Compiler forces you to define both method.
   // Otherwise you can't have a Cat object

   // All code local to the cat belongs to the cat.

};

При помощи полиморфизма можно протестировать класс Животных.
Тогда тест каждый из производных классов отдельно.

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

72
ответ дан 9 revs 27 November 2019 в 03:05
поделиться

Не бояться...

я предполагаю, что Ваша проблема связана со знакомством, не технологией. Ознакомьте себя с ООП C++.

C++ является языком ООП

Среди его нескольких парадигм, он имеет функции OOP и более, чем в состоянии поддерживать сравнение с самым чистым языком OO.

не позволяют "C, часть в C++" заставляют Вас полагать, что C++ не может иметь дело с другими парадигмами. C++ может обработать много парадигм программирования вполне любезно. И среди них, C++ ООП является самым зрелым из парадигм C++ после процедурной парадигмы (т.е. вышеупомянутое "C часть").

Полиморфизм хорошо для производства

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

переключатель и полиморфизм [почти] подобны...

... Но полиморфизм удалил большинство ошибок.

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

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

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

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

Избегают использования RTTI, чтобы найти, что RTTI типа

объекта является интересным понятием и может быть полезным. Но большую часть времени (т.е. 95% времени), переопределение метода и наследование будут более чем достаточно, и большая часть Вашего кода даже не должна знать точный тип обработанного объекта, но доверять ему, чтобы сделать правильную вещь.

, Если Вы используете RTTI в качестве прославленного переключателя, Вы упускаете суть.

(Правовая оговорка: Я - большой поклонник понятия RTTI и dynamic_casts. Но нужно использовать правильный инструмент для задачи под рукой, и большую часть времени RTTI используется в качестве прославленного переключателя, который является неправильным)

, Выдерживают сравнение динамичный по сравнению со статическим полиморфизмом

, Если Ваш код не знает точный тип объекта во время компиляции, то используйте динамический полиморфизм (т.е. классическое наследование, виртуальное переопределение методов, и т.д.)

, Если Ваш код знает тип во время компиляции, то, возможно, Вы могли использовать статический полиморфизм, т.е. шаблон CRTP http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern

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

Производственный пример кода

А кодируют подобный этому (из памяти) используется на производстве.

более легкое решение, вращаемое вокруг процедура, названная циклом сообщения (WinProc в Win32, но я записал simplier версию для пользы простоты). Поэтому подведите итог, это было что-то как:

void MyProcedure(int p_iCommand, void *p_vParam)
{
   // A LOT OF CODE ???
   // each case has a lot of code, with both similarities
   // and differences, and of course, casting p_vParam
   // into something, depending on hoping no one
   // did a mistake, associating the wrong command with
   // the wrong data type in p_vParam

   switch(p_iCommand)
   {
      case COMMAND_AAA: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_BBB: { /* A LOT OF CODE (see above) */ } break ;
      // etc.
      case COMMAND_XXX: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_ZZZ: { /* A LOT OF CODE (see above) */ } break ;
      default: { /* call default procedure */} break ;
   }
}

Каждое добавление команды добавило случай.

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

Настолько смесительный случаи был риск для эволюции.

я разрешил проблему при помощи Шаблона "команда", то есть, создав основной Объект команды, с одним процессом () метод.

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

void MyProcedure(int p_iCommand, void *p_vParam)
{
   switch(p_iCommand)
   {
      // Only one case. Isn't it cool?
      case COMMAND:
         {
           Command * c = static_cast<Command *>(p_vParam) ;
           c->process() ;
         }
         break ;
      default: { /* call default procedure */} break ;
   }
}

И затем, для каждой возможной команды, вместо того, чтобы добавить код в процедуре и смешать (или хуже, копия/вставка) код от подобных команд, я создал новую команду и получил его или из Объекта команды или из одного из его производных объектов:

ведомый к иерархии (представленный как дерево):

[+] Command
 |
 +--[+] CommandServer
 |   |
 |   +--[+] CommandServerInitialize
 |   |
 |   +--[+] CommandServerInsert
 |   |
 |   +--[+] CommandServerUpdate
 |   |
 |   +--[+] CommandServerDelete
 |
 +--[+] CommandAction
 |   |
 |   +--[+] CommandActionStart
 |   |
 |   +--[+] CommandActionPause
 |   |
 |   +--[+] CommandActionEnd
 |
 +--[+] CommandMessage

Теперь, все, что я должен был сделать, должно было переопределить процесс для каждого объекта.

Простой, и легкий расшириться.

, Например, скажите, что CommandAction, как предполагалось, сделал свой процесс в трех фазах: "прежде", "в то время как" и "после". Его код был бы чем-то как:

class CommandAction : public Command
{
   // etc.
   virtual void process() // overriding Command::process pure virtual method
   {
      this->processBefore() ;
      this->processWhile() ;
      this->processAfter() ;
   }

   virtual void processBefore() = 0 ; // To be overriden

   virtual void processWhile()
   {
      // Do something common for all CommandAction objects
   }

   virtual void processAfter()  = 0 ; // To be overriden

} ;

И, например, CommandActionStart мог быть кодирован как:

class CommandActionStart : public CommandAction
{
   // etc.
   virtual void processBefore()
   {
      // Do something common for all CommandActionStart objects
   }

   virtual void processAfter()
   {
      // Do something common for all CommandActionStart objects
   }
} ;

, Поскольку я сказал: Легкий понять (если прокомментировано правильно), и очень легкий расшириться.

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

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

26
ответ дан paercebal 27 November 2019 в 03:05
поделиться

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

Полиморфизм делает это возможным путем устранения тех условных операторов. Рассмотрите этот пример:

предположим у Вас есть Символьный объект, который носит Оружие. Можно записать метод нападения как это:

If (weapon is a rifle) then //Code to attack with rifle else
If (weapon is a plasma gun) //Then code to attack with plasma gun

и т.д.

С полиморфизмом Символ не должен "знать", что тип оружия, просто

weapon.attack()

работал бы. Что происходит, если новое оружие было изобретено? Без полиморфизма необходимо будет изменить условный оператор. С полиморфизмом необходимо будет добавить новый класс и оставить протестированный Класс символов в покое.

10
ответ дан Vincent Ramdhanie 27 November 2019 в 03:05
поделиться

Я - немного скептик: Я полагаю, что наследование часто добавляет больше сложности, чем это удаляет.

я думаю, что Вы задаете хороший вопрос, тем не менее, и одну вещь, которую я рассматриваю, это:

Вы разделяете на несколько классов, потому что Вы имеете дело с различным вещи ? Или это - действительно то же самое, действующее в различном путь ?

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

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

8
ответ дан rice 27 November 2019 в 03:05
поделиться

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

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

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

5
ответ дан JohnMcG 27 November 2019 в 03:05
поделиться

Полиморфизм является одним из угловых камней OO и конечно очень полезен. Путем деления опасений по поводу нескольких классов Вы создаете изолированные и тестируемые единицы. Таким образом вместо того, чтобы делать переключатель... случай, где Вы называете методы на нескольких различных типах или implemenations, Вы создаете объединенный интерфейс, имея несколько реализаций. Когда необходимо добавить реализацию, Вы не должны изменять клиенты, как имеет место с переключателем... случай. Очень важный, поскольку это помогает избежать регрессии.

можно также упростить клиентский алгоритм путем контакта со всего одним типом: интерфейс.

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

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

2
ответ дан QBziZ 27 November 2019 в 03:05
поделиться

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

0
ответ дан 27 November 2019 в 03:05
поделиться

Это главным образом относится к инкапсуляции знания. Давайте запустимся с действительно очевидного примера - toString (). Это - Java, но легко передает C++. Предположим, что Вы хотите распечатать человеческую дружественную версию объекта для отладки целей. Вы могли сделать:

switch(obj.type): {
case 1: cout << "Type 1" << obj.foo <<...; break;   
case 2: cout << "Type 2" << ...

Это однако ясно было бы глупо. Почему один метод должен где-нибудь знать, как распечатать все. Для самого объекта часто будет лучше знать, как распечатать себя, например:

cout << object.toString();

Тот путь toString () может получить доступ к членским полям, не нуждаясь в бросках. Они могут быть протестированы независимо. Они могут быть изменены легко.

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

1
ответ дан Nick Fortescue 27 November 2019 в 03:05
поделиться

Это работает очень хорошо , если Вы понимаете его .

существует также 2 разновидности полиморфизма. Первое очень легко понять в java-esque:

interface A{

   int foo();

}

final class B implements A{

   int foo(){ print("B"); }

}

final class C implements A{

   int foo(){ print("C"); }

}

B и C совместно используют единый интерфейс. B и C в этом случае не может быть расширен, таким образом, Вы всегда уверены, какое нечто () Вы называете. То же идет для C++, просто сделайте A:: нечто, чистое виртуальный.

116-секундный, и более хитрый полиморфизм во время выполнения. Это не выглядит слишком плохо в псевдокоде.

class A{

   int foo(){print("A");}

}

class B extends A{

   int foo(){print("B");}

}

class C extends B{

  int foo(){print("C");}

}

...

class Z extends Y{

   int foo(){print("Z");

}

main(){

   F* f = new Z();
   A* a = f;
   a->foo();
   f->foo();

}

, Но это намного более хитро. Особенно, если Вы работаете в C++, где некоторые объявления нечто могут быть виртуальными, и часть наследования могла бы быть виртуальной. Также ответ на это:

A* a  = new Z;
A  a2 = *a;
a->foo();
a2.foo();

не мог бы быть тем, что Вы ожидаете.

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

0
ответ дан Martin York 27 November 2019 в 03:05
поделиться

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

Также выезд книга "Martin Fowlers" по "Рефакторингу"
Используя переключатель вместо полиморфизма является запахом кода.

0
ответ дан 27 November 2019 в 03:05
поделиться

Это действительно зависит от Вашего стиля программирования. В то время как это может быть корректно в Java или C#, я не соглашаюсь, что автоматически решение использовать полиморфизм корректно. Можно разделить код на большое количество небольших функций и выполнить поиск массива с указателями функции (инициализированный во время компиляции), например. В C++ полиморфизм и классы часто злоупотребляются - вероятно, самая большая ошибка дизайна, сделанная людьми, происходящими из сильных языков ООП в C++, состоит в том, что все входит в класс - это не верно. Класс должен только содержать минимальный набор вещей, которые заставляют его работать в целом. Если подкласс или друг необходимы, пусть будет так, но они не должны быть нормой. Любые другие операции на классе должны быть бесплатными функциями в том же пространстве имен; ADL позволит этим функциям использоваться без поиска.

C++ не является языком ООП, не делайте его один. Это настолько же плохо как программирует главнокомандующего ++.

-2
ответ дан coppro 27 November 2019 в 03:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: