如何限制层次结构中的覆盖?

时间:2010-07-27 14:00:33

标签: c# .net inheritance override

这是一个例子,我只是对它将如何实现感到好奇。

我想只启用Animal的子类来设置它们拥有的腿数,但我仍然希望它们能够设置自己的颜色。因此,我希望从层次结构中进一步限制类,然后再更改此Legs属性。

public abstract class Animal
{
    public string Colour { get; protected set; }
    public int Legs { get; protected set; }

    public abstract string Speak();
}

public class Dog : Animal
{
    public Dog()
    {
        Legs = 4;
    }

    public override string Speak()
    {
        return "Woof";
    }
}

public sealed class Springer : Dog
{
    public Springer()
    {
        Colour = "Liver and White";
    }
}

public sealed class Chihuahua : Dog
{
    public Chihuahua()
    {
        Colour = "White";
    }

    public override string Speak()
    {
        return "*annoying* YAP!";
    }
}

例如,我想要消除这种子类:

public sealed class Dalmatian : Dog
{
    public Dalmatian()
    {
        Legs = 20;
        Colour = "Black and White";
    }
}

如何实现这一目标?

我知道我可以通过在父类中密封函数的实现来停止覆盖子类。我用Legs属性尝试了这个,但我无法让它工作。

由于

10 个答案:

答案 0 :(得分:11)

在某种程度上,这违背了OO原则。您的超类Animal提供了一个合同,其中包括Legs的set / get。然后,您希望子类能够限制该接口禁止设置Legs。由于子类化提供了“is-a”关系,限制接口就违背了这一点,这意味着子类不是真正的子类型,因为不存在set Legs方法。

我会从Animal中删除Legs属性的setter,因为这是一个实现细节。相反,只需要一个抽象的吸气剂。然后,子类可以通过返回硬编码值或使用字段来存储值来决定如何最好地实现它。

答案 1 :(得分:10)

不要让Legs成为抽象类的字段,而应该只将它设为属性(删除setter),并使其成为抽象的。

在动物

public abstract int Legs { get; }

在狗中

public override sealed int Legs { get { return 4; } }

答案 2 :(得分:5)

在Java中,你可以使getter和setter成为最终方法,因此无法覆盖它们。在C#中,我相信你想要的关键词是“密封的”;你要密封方法,但不是整个子类。

您将变量本身设为私有,因此子类必须使用getter / setter来访问变量。

答案 3 :(得分:4)

class Quadruped : Animal
{
    public int Legs { get {return 4;} }
}

class Dog : Quadruped
{
    ...
}

我想你永远不想将章鱼归类为四足动物。

我认为如果你遇到这类问题,你需要重新安排层次结构。

答案 4 :(得分:4)

你的问题暗示这些类代表理想动物而非实际的动物实例 - 毕竟,个体动物的腿数可变。

如果是这种情况,那么您首先不需要Legs的设置器。 setter是一个设计错误,因为语义是错误的:没有调用者,包括子类,应该能够在任意时间设置leg的数量。

相反,要求受保护的Animal构造函数中的腿数(也可能是颜色):

public abstract class Animal {
    protected Animal(int legs) {
        this.legs = legs;
    }
}

public class Dog: Animal {     
    public Dog(): base(4) {}
}

如果你以后决定Dog子类需要能够设置它,你可以添加一个允许它的新构造函数。

答案 5 :(得分:1)

//overload Leg property in Dog class and make set as private
public abstract class Animal
{
    public string Colour { get; protected set; }
    private int legs;
    public int Legs
    {
        get { return legs; }
        protected set { legs = value; }
    }

    //public int Legs { get; protected set; }

    public abstract string Speak();
}
public class Dog : Animal
{
    public int Legs
    {
        get { return base.Legs; }

        private set { base.Legs = value; }
    }
    public Dog()
    {
        Legs = 4;
    }
}

答案 6 :(得分:1)

基本上,使用C#无法做到这一点,正如本主题中的几个帖子所说的那样。有人认为这不是适当的OO,但我不同意。为便于参考,如果您方便,请查看OOSC的第464+页。这里使用的术语是不变继承。给出的示例是Rectangle(总是四边),继承自Polygon(大于2的任意数量的边)。

规则很简单,引用:

  

类的不变属性是   断言的布尔and   出现在它不变的条款和   它的不变性质   父母,如果有的话。

Bertrand Meyer使用Design By Contract。在较小的范围内,这也适用于C#。使用.NET 4.0,它has become available through Spec#

关于这是不正确的OO 的论点:参数是正确的(阻止链中的继承违反合同),但不阻止继承,但通过使用不变子句添加对值的限制,OO范例得以保存,继承链保持不变。像这样:

abstract class Animal
{
    public abstract Legs { get; }
}

class Dog : Animal
{
    public Dog { } 

    [InvariantMaximum(4), InvariantMinimum(4)]
    public override Legs { get { return 4; } }
}

class Labrador : Dog
{
    public override Legs { get { return 5; } }    // compiler error
}

class Chihuahua: Dog
{
    public override Legs { get { return 4; } }    // OK
}

编辑(密封的解决方案,this的后续行动)

正如其中一个主题所要求的那样,这里有一个小例子可以封锁成员的进一步继承(这里许多人认为是违反OO,而语言设计者清楚地知道它不是):< / p>

public abstract class Animal
{
    public abstract int Legs {get;}
}

public class Dog : Animal
{
    public sealed override int Legs {get { return 4; } }
}

public class Labrador : Dog
{
    public override int Legs { get; }    // compiler error
}

答案 7 :(得分:0)

您可能还会考虑如果有人创建变量或参数或输入Animal然后尝试设置其Legs属性会发生什么。如果它是其中一个不允许设置其腿的子类,你会抛出一个特定的异常吗?

例如。

public void SpeedUpMyAnimal(Animal animal) {
    animal.Legs *= 2;
}

如果Animal.Legs是公共财产,我完全有理由相信这会有效。但是,如果来电者经过一只狗怎么办?

答案 8 :(得分:0)

在Animal的子类中,使Leg属性的set访问器成为私有。 [注意:在Animal Class中,Leg属性必须是虚拟的。]

    public abstract class Animal
    {
        public string Colour { get; protected set; }
        public virtual int Legs { get; protected set; }

        public abstract string Speak();
    }

    public class Dog : Animal
    {
        public Dog()
        {
            Legs = 4;
        }

        public override int Legs
        {
            get
            {
                return base.Legs;
            }
            private set
            {
                base.Legs = value;
            }
        }

        public override string Speak()
        {
            return "Woof";
        }
    }

这将阻止Dog的任何衍生物设置Leg属性。

答案 9 :(得分:0)

如上所述,正确的方法是使腿数成为抽象的只读属性。为了使得腿数不能被覆盖的狗,我认为有必要创建一个中级类,其目的是为NumberOfLegs属性定义一个实现,然后使用公共NumberOfLegs函数定义Dog类,该函数隐藏基数阶级财产。任何超越腿数的努力都将指向shadow属性,因此原始内容无法再被覆盖。

如果可以为同一范围内的同一属性定义不同的Overrides和Shadows实现(在基类中访问属性时仅使用Overrides),那会更好但我不知道任何方式这样做。