Почему наследование не работает так, как я думаю, оно должно работать? - PullRequest
8 голосов
/ 06 сентября 2008

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

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }

Это позволило бы любому, использующему класс Dog, автоматически получать DogLegs, и любому, кто использует класс Animal, чтобы получать Legs. Проблема в том, что переопределенная функция должна иметь тот же тип, что и базовый класс, поэтому она не будет компилироваться. Я не понимаю, почему этого не следует делать, поскольку DogLeg неявно преобразуется в Leg. Я знаю, что есть много способов обойти это, но мне более любопытно, почему это невозможно / реализовано в C #.

РЕДАКТИРОВАТЬ : Я немного изменил это, так как на самом деле я использую свойства вместо функций в моем коде.

РЕДАКТИРОВАТЬ : я изменил его обратно на функции, потому что ответ относится только к этой ситуации (ковариация по параметру значения функции набора свойства не должна работать ). Извините за колебания! Я понимаю, что многие ответы кажутся неуместными.

Ответы [ 17 ]

15 голосов
/ 06 сентября 2008

Короткий ответ: GetLeg инвариантен в типе возвращаемого значения. Длинный ответ можно найти здесь: Ковариация и контравариантность

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

12 голосов
/ 06 сентября 2008

Очевидно, что вам понадобится приведение, если вы работает на сломанном DogLeg.

6 голосов
/ 06 сентября 2008

Собака должна вернуть Leg, а не DogLeg в качестве типа возврата. Фактическим классом может быть DogLeg, но смысл в том, чтобы отделить его, чтобы пользователю Dog не нужно было знать о DogLegs, ему нужно знать только о Legs.

Изменение:

class Dog : Animal
{
  public override DogLeg GetLeg() {...}
}

до:

class Dog : Animal
{
  public override Leg GetLeg() {...}
}

Не делай этого:

 if(a instanceof Dog){
       DogLeg dl = (DogLeg)a.GetLeg();

это отрицает цель программирования для абстрактного типа.

Причина скрытия DogLeg заключается в том, что функция GetLeg в абстрактном классе возвращает Abstract Leg. Если вы переопределяете GetLeg, вы должны вернуть Leg. Вот в чем смысл наличия метода в абстрактном классе. Пропагандировать этот метод для детей. Если вы хотите, чтобы пользователи Dog знали о DogLegs, создайте метод GetDogLeg и верните DogLeg.

Если вы МОЖЕТЕ сделать так, как задает вопрос, то каждый пользователь Animal должен знать обо ВСЕХ животных.

4 голосов
/ 06 сентября 2008

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

Но C # пока не поддерживает "ковариантные типы возврата" в переопределенных методах (в отличие от C ++ [1998] и Java [2004]).

Вам нужно будет обойтись и обойтись в обозримом будущем, как заявил Эрик Липперт в его блоге [19 июня 2008 года]:

Этот тип дисперсии называется "ковариацией возвращаемого типа".

У нас нет планов по внедрению такого рода дисперсии в C #.

3 голосов
/ 06 сентября 2008

Вы можете использовать обобщения и интерфейсы для реализации этого в C #:

abstract class Leg { }

interface IAnimal { Leg GetLeg(); }

abstract class Animal<TLeg> : IAnimal where TLeg : Leg
 { public abstract TLeg GetLeg();
   Leg IAnimal.GetLeg() { return this.GetLeg(); }
 }

class Dog : Animal<Dog.DogLeg>
 { public class DogLeg : Leg { }
   public override DogLeg GetLeg() { return new DogLeg();}
 } 
3 голосов
/ 06 сентября 2008
abstract class Animal
{
  public virtual Leg GetLeg ()
}

abstract class Leg { }

class Dog : Animal
{
  public override Leg GetLeg () { return new DogLeg(); }
}

class DogLeg : Leg { void Hump(); }

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

Leg myleg = myDog.GetLeg();

Тогда, если вам нужно, вы можете разыграть его:

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }

Полностью надуманный, но суть в том, что вы можете сделать это:

foreach (Animal a in animals)
{
   a.GetLeg().SomeMethodThatIsOnAllLegs();
}

Все еще сохраняя возможность использовать специальный метод Горба на Доглегах.

2 голосов
/ 06 сентября 2008

Концепция, которая вызывает у вас проблемы, описана в http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

2 голосов
/ 06 сентября 2008

GetLeg () должен вернуть Leg для переопределения. Однако ваш класс Dog по-прежнему может возвращать объекты DogLeg, поскольку они являются дочерним классом Leg. клиенты могут затем использовать их в качестве собачьих ног.

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}
2 голосов
/ 06 сентября 2008

Не то, чтобы это было много пользы, но, возможно, интересно отметить, что Java поддерживает ковариантные возвраты, и поэтому это будет работать именно так, как вы надеялись. За исключением того, что у Java нет свойств;)

1 голос
/ 16 сентября 2008

C # имеет явные реализации интерфейса для решения этой проблемы:

abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

class Dog : IAnimal
{
    public override DogLeg GetLeg() { /* */ }

    Leg IAnimal.GetLeg() { return GetLeg(); }
}

Если у вас есть Dog по ссылке типа Dog, то вызов GetLeg () вернет DogLeg. Если у вас есть тот же объект, но ссылка имеет тип IAnimal, то он вернет Leg.

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