为什么继承不能像我认为的那样起作用?

时间:2020-03-05 18:48:33  来源:igfitidea点击:

我遇到了一些继承问题,因为我有一组相互关联的抽象类,需要将它们全部一起重写以创建客户端实现。理想情况下,我想执行以下操作:

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#中实现。

编辑:我做了一些修改,因为我实际上在代码中使用属性而不是函数。

编辑:我将其更改回函数,因为答案仅适用于这种情况(属性的set函数的value参数的协方差不起作用)。对不起波动!我意识到这使得很多答案似乎无关紧要。

解决方案

回答

GetLeg()必须返回Leg作为重写。但是,由于Dog类是Leg的子类,因此Dog类仍然可以返回DogLeg对象。然后,客户可以像狗狗一样投掷并对其进行操作。

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}

回答

Dog应该返回腿而不是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函数返回了抽象腿。如果要覆盖GetLeg,则必须返回一条Leg。那就是在抽象类中具有方法的意义。将该方法传播给孩子。如果我们希望Dog的用户了解DogLegs,请创建一个名为GetDogLeg的方法并返回DogLeg。

如果我们可以按问问者的意愿进行操作,那么Animal的每个用户都需要了解所有动物。

回答

是的,我知道我可以施放,但这意味着客户必须知道Dogs有DogLegs。我想知道的是,鉴于存在隐式转换,是否有技术原因无法做到这一点。

回答

简短的答案是GetLeg的返回类型不变。长答案可以在这里找到:协方差和协方差

我想补充一点,虽然继承通常是大多数开发人员从其工具箱中提取的第一个抽象工具,但几乎总是可以使用组合来代替。对于API开发人员而言,组合工作要稍微多一些,但会使API对它的使用者更有用。

回答

@布莱恩·莱希(Brian Leahy)
显然,如果我们仅以支腿的形式进行操作,则没有必要或者理由进行铸造。但是,如果存在某些DogLeg或者Dog特定的行为,则有时出于某些原因,必须进行强制转换。

回答

在http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)中描述了导致我们出现问题的概念。

回答

@卢克

我认为我们可能对继承有误解。 Dog.GetLeg()将返回DogLeg对象。

public class Dog{
    public Leg GetLeg(){
         DogLeg dl = new DogLeg(super.GetLeg());
         //set dogleg specific properties
    }
}

    Animal a = getDog();
    Leg l = a.GetLeg();
    l.kick();

实际调用的方法是Dog.GetLeg();和DogLeg.Kick()(假设存在Leg.kick()方法),则不需要声明的返回类型为DogLeg,因为即使Dog.GetLeg()的返回类型为腿。

回答

我们还可以返回Leg和/或者DogLeg都实现的接口ILeg。

回答

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();
}

同时仍然保留对Doglegs使用特殊驼峰方法的能力。

回答

也许通过示例更容易看到问题:

Animal dog = new Dog();
dog.SetLeg(new CatLeg());

现在,如果我们是Dog编译的,那应该可以编译,但是我们可能不想要这样的变体。

一个相关的问题是Dog []应该是Animal [],还是IList <Dog>是IList <Animal>?

回答

要记住的重要一点是,我们可以在使用基本类型的每个位置使用派生类型(可以将Dog传递给需要Animal的任何方法/属性/字段/变量)

让我们使用以下功能:

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}

一个完美有效的函数,现在让我们这样调用该函数:

AddLeg(new Dog());

如果属性Dog.Leg不是Leg类型,则AddLeg函数突然包含错误,无法编译。

回答

并不是说它有很多用处,但是可能有趣的是注意到Java确实支持协变量返回,因此这完全可以按照期望工作。显然除了Java没有属性之外;)

回答

显然,如果我们是
在破碎的DogLeg上操作。

回答

我们可以使用泛型和接口在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();}
 }

回答

使签名的重写方法具有返回类型是重写方法(phew)中返回类型的子类型,这是一个完全正确的愿望。毕竟,它们是运行时类型兼容的。

但是C尚不支持重写方法中的"协变量返回类型"(与C ++ [1998]和Java [2004]不同)。

正如埃里克·利珀特(Eric Lippert)在他的博客中所说,我们需要努力工作并为可预见的未来做好准备。
[2008年6月19日]:

That kind of variance is called "return type covariance". 
  
  we have no plans to implement that kind of variance in C#.

回答

我们可以通过使用具有适当约束的泛型来实现所需的目标,如下所示:

abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

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

class DogLeg : Leg { }

回答

跟踪显式接口实现以解决此问题:

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。