Generics 并行继承链中的多态性与类型安全
我有两个平行的继承链:Generics 并行继承链中的多态性与类型安全,generics,inheritance,polymorphism,strong-typing,Generics,Inheritance,Polymorphism,Strong Typing,我有两个平行的继承链: Chain1: Animal <- Lion <- Gazelle Chain2: Food <- Meat <- Grass 但是,此代码不是类型安全的。如果我用草喂我的狮子,我只会在运行时遇到我的bug 有人能给我提供一个代码示例,在不牺牲多态性的情况下使用泛型来促进类型安全吗?嗯,也许您可以修改您的第一个继承链: 畜生 -食肉动物 -狮子 -老虎 - ... -食草动物 -羊 然后,你可以这样做: publ
Chain1:
Animal <- Lion
<- Gazelle
Chain2:
Food <- Meat
<- Grass
但是,此代码不是类型安全的。如果我用草喂我的狮子,我只会在运行时遇到我的bug
有人能给我提供一个代码示例,在不牺牲多态性的情况下使用泛型来促进类型安全吗?嗯,也许您可以修改您的第一个继承链: 畜生 -食肉动物 -狮子 -老虎 - ... -食草动物 -羊 然后,你可以这样做:
public class Animal<T> where T : Food
{
public abstract T Eats { get; set; }
}
public class Carnivore : Animal<Meat>
{
...
}
公共级动物,其中T:食物
{
公共抽象T{get;set;}
}
公共级食肉动物:动物
{
...
}
我还没有测试,这只是我的想法…动物可以是一个通用类:
public abstract class Animal<T> where T : Food
{
public abstract T Eats {get;set;}
}
公共抽象类动物,其中T:Food
{
公共抽象T{get;set;}
}
然后你可以把狮子变成这样的肉食动物
public class Lion : Animal<Meat>
{
//etc...
}
公共类狮子:动物
{
//等等。。。
}
但这并非最佳解决方案。您不能再将animal用作多态接口,因为要使用它,您需要了解有关其实现的详细信息。这可能不是多态性的地方。我认为这是一个错误的两难选择。食物似乎更接近于一个接口而不是一个抽象的基类,因为它听起来根本不像肉和草。取而代之的是,考虑一下:
public interface IFood {
public boolean IsForCarnivores();
}
public class Lion : Animal {
...
public override IFood Eats
{
get { ... }
set
{
if (value.IsForCarnivores()) DoSomething(value);
else throw new Exception("I can't eat this!");
}
}
}
使用组合而不是继承: 与其基于消化系统进行继承,不如将消化分解为自己的一组类。
首先,一个描述不同饮食方式的界面
public interface IDigest
{
void Eat(Meat food);
void Eat(Plant food);
void Eat(Offal food); //lol nethack
}
食肉动物吃肉,有时吃草药,不喜欢吃垃圾:
public class Carnivorous : IDigest
{
public void Eat(Meat food)
{
Console.Write("NOM NOM");
}
public void Eat(Plant food)
{
if(Starving)
Console.Write("Ugh, better than nothing.");
else
Vomit();
}
public void Eat(Offal food)
{
Vomit();
}
}
食草动物很挑剔,宁愿死也不愿吃肉(我知道,保留你的评论,这就是一个例子)
杂食动物什么都吃。见证一场州博览会
public class Omnivorous : IDigest
{
public void Eat(Meat food)
{
Console.Write("NOM NOM");
}
public void Eat(Plant food)
{
Console.Write("NOM NOM");
}
public void Eat(Offal food)
{
Console.Write("NOM NOM");
}
}
所有动物都必须进食,因此它们必须有消化系统和其他系统
public abstract class Animal
{
/* lots of other stuff */
public IConsume DigestiveSystem {get;set;}
/* lots of other stuff */
}
嬉皮士是一种具有已知品味的动物;它在实例化时进行自我配置。也可以从外部注入行为和系统
public class Hippie : Animal
{
public Hippie()
{
/*stuff*/
DigestiveSystem = new Herbivore();
BodyOdorSystem = new Patchouli();
/*more stuff*/
}
}
最后,让我们看看嬉皮士吃汉堡
public static void Main()
{
Hippie dippie = new Hippie();
Meat burger = new Meat("Burger", 2/*lb*/);
dippie.DigestiveSystem.Eat(burger);
}
在为动物等复杂系统建模时,我更喜欢组合而不是继承。复杂系统可以快速分解继承树。以三种动物系统为例:杂食/食草动物/食肉动物、水/空气/陆地和夜间/日间。我们甚至不用担心如何决定哪种分类成为动物的第一个区别点。我们是先把动物引向食肉动物,先引向水生物,还是先引向夜间活动
由于杂食动物可以生活在空气中,喜欢夜间活动(蝙蝠*),也可以是白天行走的陆地生物(人类),因此你必须有一条符合每个选项的遗传路径。这是一个已经有54种不同类型的继承树(它很早,很好)。动物比这复杂得多。您可以很容易地得到一个具有数百万类型的继承树。绝对是合成而不是继承
*例如,新西兰短尾蝙蝠是杂食性的 狮子不吃草?至少我们的猫过去偶尔吃草;-)这不会使Eats属性像原始问题中那样具有任何类型安全性。如果你给狮子喂草,你仍然不会得到编译错误。此外,这不是错误的困境,而是涵盖了现实生活中的场景。实际上,您没有回答正确的问题,这是关于并行继承链的问题。(动物王国只是多态性的典型教科书例子)。如果要考虑复杂的继承问题,我会选择本体论。但我奖励你幽默的例子!答案已经给出并被接受。我对展示另一种设计更感兴趣。继承很强大,但很容易被滥用。
public class Hippie : Animal
{
public Hippie()
{
/*stuff*/
DigestiveSystem = new Herbivore();
BodyOdorSystem = new Patchouli();
/*more stuff*/
}
}
public static void Main()
{
Hippie dippie = new Hippie();
Meat burger = new Meat("Burger", 2/*lb*/);
dippie.DigestiveSystem.Eat(burger);
}