Почему использование как базового класса, так и интерфейса в полиморфизме?

В примере полиморфизма С# существует класс Cat, который наследует класс AnimalBase и интерфейс IAnimal.

Данная ссылка: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming

Мой вопрос: почему используется как базовый класс, так и используемый интерфейс? Почему не тот или другой? Я думал о том, что для реализации полиморфизма потребуется только абстрактный класс.

Спасибо

Ответ 1

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

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

Позвольте мне продемонстрировать:

На динамически типизированном (или утином) языке (например, Smalltalk) все переменные являются ссылками на объекты (не что иное и не более.) Итак, в Smalltalk я могу это сделать:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

Этот код:

  • Объявляет локальную переменную, называемую anAnimal (обратите внимание, что мы НЕ указываем TYPE переменной - все переменные являются ссылками на объект, не более и не менее.)
  • Создает новый экземпляр класса с именем "Pig"
  • Назначает новый экземпляр Duck переменной anAnimal.
  • Отправляет сообщение makeNoise свиньи.
  • Повторяет все это с помощью коровы, но присваивает ей ту же самую точную переменную, что и Pig.

Тот же Java-код будет выглядеть примерно так (делая предположение, что Duck and Cow являются подклассами Animal:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

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

В Smalltalk мы можем написать это:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

Это хорошо работает в Smalltalk, потому что он утиный (если он ходит как утка, а quacks - как утка - это утка.) В этом случае, когда сообщение отправляется объекту, поиск выполняется в списке методов приемника, и если найден метод сопоставления, он вызывается. Если нет, то возникает какое-то исключение NoSuchMethodError, но все это делается во время выполнения.

Но на Java, статически типизированный язык, какой тип мы можем назначить нашей переменной? Кукуруза должна унаследовать от Vegetable, чтобы поддержать рост, но не может унаследовать от Animal, потому что она не шумит. Корова должна наследовать от Animal для поддержки makeNoise, но не может наследовать от Vegetable, потому что она не должна внедрять урожай. Похоже, нам нужно многократное наследование - способность наследовать более чем один класс. Но это оказывается довольно сложной функцией языка из-за всех появляющихся красных случаев (что происходит, когда несколько параллельных суперклассов реализуют один и тот же метод?) И т.д.)

Вперед интерфейсы...

Если мы создадим классы животных и овощей, каждый из которых будет реализовывать Growable, мы можем заявить, что наша Корова - это Животное, а наша кукуруза - Овощи. Мы также можем заявить, что как животные, так и овощи растут. Это позволяет нам писать это, чтобы вырастить все:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

И это позволяет нам делать это, чтобы сделать шумы животных:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

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

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

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

Animal farmObject = new Corn();  // Compiler error: Corn cannot be cast to Animal.
farmObject makeNoise();

-

Animal farmObject = new Cow();
farmObject.harvest(); // Compiler error: Animal doesn't have the harvest message.

Итак... чтобы подвести итог:

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

  • Интерфейсы дают нам много преимуществ "истинного" полиморфизма, не жертвуя проверкой типа компилятора.

Ответ 2

Базовые классы используются, когда вы хотите повторно использовать ПОВЕДЕНИЕ

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

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

Ответ 3

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

Я не вижу противоречия в том, что и то, и другое.

Ответ 4

Интерфейсы предоставляют вам возможность иметь полиморфное поведение в классе heirachy's. Недостатком является то, что вы не можете наследовать реализацию по умолчанию (напрямую). С полиморфизмом класса вы можете получить только это полиморфное поведение в вашем классе heirachy, но вы можете наследовать поведение common/default. Наследуя и предоставляя интерфейс, вы предоставляете контракт (интерфейс), но получаете легкие преимущества реализации наследования, в то же время позволяя другим поддерживать "контракт" за пределами ограничений базового класса.

Ответ 5

Интерфейсы обеспечивают соблюдение "Поведения". Любой класс, объявленный для реализации указанного интерфейса, ДОЛЖЕН реализовать участников с сигнатурами, объявленными в интерфейсе. То есть они должны публично описывать поведение... Они не обязательно должны реализовывать поведение таким же образом, но они musrt будет способен к одному и тому же поведению... т.е. и птица, и червь CanMove, поэтому они оба должны реализовать поведение "возможности" Move ", указав, что оба они должны impement интерфейс ICanMove делает это...  Как они это делают, это функция реализации.

Базовые классы предназначены для повторного использования "Реализация"...

Итак, для обозначения соглашений для интерфейсов предлагается использовать "I [Verb/Adverb]", как в IEnumerable, ICanMove, ICanLog и т.д.

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

Ответ 6

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

Ответ 7

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