Почему использование как базового класса, так и интерфейса в полиморфизме? - PullRequest
1 голос
/ 19 декабря 2008

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

Ссылка в вопросе: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming

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

Спасибо

Ответы [ 7 ]

7 голосов
/ 19 декабря 2008

Базовые классы используются, когда вы хотите повторно использовать BEHAVIOR

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

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

7 голосов
/ 19 декабря 2008

Утверждение, что «наследование от базового класса позволяет вам наследовать BEHAVIOR, тогда как реализация интерфейса позволяет только указывать ВЗАИМОДЕЙСТВИЕ», абсолютно верно.

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

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

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

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

anAnimal := Cow new.
anAnimal makeNoise.

Этот код:

  1. Объявляет локальную переменную с именем anAnimal (обратите внимание, что мы НЕ УКАЗЫВАЕМ ТИП переменной - все переменные являются ссылками на объект, не больше и не меньше.)
  2. Создает новый экземпляр класса с именем "Свинья"
  3. Назначает этот новый экземпляр Duck переменной anAnimal.
  4. Отправляет сообщение makeNoise свинье.
  5. Повторяет все это, используя корову, но присваивая ее той же переменной, что и Свинья.

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

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

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

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

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

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

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

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

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

Вдоль интерфейсов ...

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

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.

Итак .... подведем итог:

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

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

3 голосов
/ 19 декабря 2008

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

Я не вижу никакого противоречия в том, чтобы иметь оба.

1 голос
/ 19 декабря 2008

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

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

Вот почему соглашения об именах для интерфейсов предлагают использовать «I [Verb / Adverb]», как в IEnumerable, ICanMove, ICanLog и т. Д.

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

1 голос
/ 19 декабря 2008

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

0 голосов
/ 19 декабря 2008

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

0 голосов
/ 19 декабря 2008

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

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