Почему наследование и полиморфизм так широко используются?

18

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

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

Но этого гораздо проще достичь с помощью утилитарной типизации, будь то динамический язык, такой как Python, или статический язык, такой как C ++.

В качестве примера рассмотрим следующую функцию Python, за которой следует ее статический эквивалент C ++:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

Эквивалент ООП будет выглядеть примерно так:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

Основное различие, конечно, заключается в том, что версия Java требует создания интерфейса или иерархии наследования, прежде чем она заработает. Таким образом, версия Java требует больше работы и является менее гибкой. Кроме того, я считаю, что большинство реальных иерархий наследования несколько нестабильны. Мы все видели надуманные примеры фигур и животных, но в реальном мире, когда меняются бизнес-требования и добавляются новые функции, трудно выполнить какую-либо работу, прежде чем вы действительно захотите расширить отношения «есть» между подклассы, или переделать / реорганизовать вашу иерархию, чтобы включить дополнительные базовые классы или интерфейсы для соответствия новым требованиям. С типизацией утки вам не нужно беспокоиться о моделировании чего-либо - вы просто беспокоитесь о функциональности, которая вам нужна.

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

Channel72
источник
Я не эксперт по Python, поэтому я должен спросить: что произойдет, если objнет doSomethingметода? Возникает ли исключение? Ничего не происходит?
FrustratedWithFormsDesigner
6
@ Разочарованный: очень четкое, конкретное исключение возникает.
дсимча
11
Разве утка не печатает просто полиморфизм, взятый до одиннадцати? Я предполагаю, что вы не разочарованы ООП, а статически типизированными воплощениями. Я действительно могу это понять, но это не делает ООП в целом плохой идеей.
3
Сначала прочитайте «широко» как «дико» ...

Ответы:

22

Я в основном согласен с тобой, но ради забавы я сыграю в «Адвокат дьявола». Явные интерфейсы дают единственное место для поиска явно, формально указанного контракта, сообщающего вам, что тип должен делать. Это может быть важно, когда вы не единственный разработчик проекта.

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

Итог: я согласен с тем, что наследование и полиморфизм в стиле Java могут быть PITA, и альтернативы следует использовать чаще, но он все же имеет свои преимущества.

dsimcha
источник
29

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

Дело не в том, что их широко преподают в школах, а наоборот: их широко преподают в школах, потому что люди (то есть рынок) обнаружили, что они работают лучше, чем старые инструменты, и поэтому школы начали их учить. [Анекдот: когда я впервые изучал ООП, было чрезвычайно трудно найти колледж, который преподавал бы любой язык ООП. Десять лет спустя было трудно найти колледж, который не преподавал бы язык ООП.]

Вы сказали:

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

Я говорю:

Нет не

То, что вы описываете, это не полиморфизм, а наследование. Не удивительно, что у вас проблемы с ООП! ;-)

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

Итак ... Duck Typing - это (или, скорее, позволяет) полиморфизм

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

Это статическая и динамическая вещь, такая же старая, как Лисп, и не ограниченная ООП.

Стивен А. Лоу
источник
2
+1 за "типирование утки - полиморфизм". Полиморфизм и наследование были подсознательно объединены слишком долго, и строгая статическая типизация - единственная реальная причина, которой они когда-либо должны были быть.
Цао
+1. Я думаю, что различие между статической и динамической характеристиками должно быть чем-то большим, чем просто примечание - оно лежит в основе различия между типизацией утки и полиморфизмом в стиле Java.
Сава Б.
8

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

Почему?

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

Это все. Там нет "ненужных ограничений". Это упрощение.

Аналогично, полиморфизм - это то, что означает «типирование утки». Те же методы. Много классов с идентичным интерфейсом, но разные реализации.

Это не ограничение. Это упрощение.

С. Лотт
источник
7

Наследование - это злоупотребление, как и утка. И то и другое может привести к проблемам.

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

ElGringoGrande
источник
3
Ваш комментарий о тестировании относится только к динамическому типу утки. Статическая типизация утки в стиле C ++ (с шаблонами) выдает ошибки во время компиляции. (Хотя предоставленные ошибки шаблона C ++ довольно трудно расшифровать, но это совсем другая проблема.)
Channel72
2

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

Затем, если вы выучили это догматически, вы можете позже «бунтовать» так же догматично в другом направлении. Это тоже не хорошо.

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

Майк Данлавей
источник
2

Да, статическая типизация и интерфейсы являются ограничениями. Но все, с тех пор как было изобретено структурированное программирование (то есть «Goto считается вредным»), было нас ограничивать. Дядя Боб отлично это оценил в своем видео-блоге .

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

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

Мартин Викман
источник
1

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

Полиморфизм является прямым следствием отношений «есть». Если Derived наследует от Base, то каждая Derived «является-a» Base, и, следовательно, вы можете использовать Derived везде, где вы используете Base. Если это не имеет смысла в иерархии наследования, тогда иерархия неверна и, вероятно, происходит слишком много наследования.

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

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

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

Дэвид Торнли
источник
0

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

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

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

Бен Хьюз
источник
-1

Истина лежит где-то посередине. Мне нравится, как C # 4.0, будучи статически типизированным языком, поддерживает "типизацию утки" по ключевому слову "dynamic"

SiberianGuy
источник
-1

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

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Здесь класс GoldenRetrieverимеет то же самое, Soundчто и Dogбесплатное спасибо за наследование.

Я напишу тот же пример с моим уровнем Haskell, чтобы вы увидели разницу

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

Здесь вы не избежите необходимости указывать soundдля GoldenRetriever. В общем, проще всего было бы

sound GoldenRetriever = sound Dog

но просто Imagen, если у вас есть 20 функций! Если есть эксперт по Haskell, пожалуйста, покажите нам более простой способ.

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

Кристиан Гарсия
источник
5
Клянусь, Animalэто анти-учебник ООП.
Теластин
@Telastyn Я узнал пример от Дерека Банаса на Youtube. Великий учитель. На мой взгляд, это очень легко визуализировать и рассуждать. Вы можете редактировать пост с лучшим примером.
Кристиан Гарсия
это, кажется, не добавляет ничего существенного по сравнению с предыдущими 10 ответами
gnat
@gnat Хотя «вещество» уже существует, человеку, плохо знакомому с предметом, может оказаться, что пример легче понять.
Кристиан Гарсия
2
Животные и фигуры - это действительно плохое применение полиморфизма по нескольким причинам, которые обсуждались на этом сайте до тошноты. По сути, говорить «кот» - это «животное» бессмысленно, поскольку нет конкретного «животного». То, что вы на самом деле подразумеваете под моделированием, это поведение, а не идентичность Определение интерфейса «CanSpeak», который может быть реализован как мяуканье или лай, имеет смысл. Имеет смысл определить интерфейс «HasArea», который могут реализовывать квадрат и круг, но не универсальный Shape.