C# 从抽象 类 访问 属性

C# acces property from abstract classes

我有这样的 class 结构:

BaseAnimal.cs:

public abstract class BaseAnimal
{
   public string? Species { get; set; }
   public double Price { get; set; }
}

然后我有这两个 classes:

public abstract class Carnivore : BaseAnimal
{
    public double MeatFood { get; set; }
}

public abstract class Herbivore : BaseAnimal
{
   public double GreenFood { get; set; }
}

然后我有子 classes:

public class Ape : Herbivore
{
    public Ape()
    {
        Species = "Ape";
        GreenFood = 10.0;
        Price = 10000.0;
    }
}

然后我有一个工厂已经在使用这条线来获取动物的所有属性:

public BaseAnimal[] Animals = prototypes.Values.ToArray();

在我的 Main class 中,我想阅读 Animals 的属性:

 private void cboAnimals_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
    Debug.WriteLine(animalFactory.Animals[0].);
 }

此代码的问题是,我无法访问 Herbivore.cs 和 Carnivore.cs

中指定的属性

AnimalsBaseAnimal的数组。此 class 没有 MeatFoodGreenFood 属性。这就是您无法访问这些属性的原因。如果您可以访问它们,则可以使用此代码:

BaseAnimal animal = new Ape();
Console.WriteLine(animal.MeatFood);

但是Ape没有MeatFood属性。如果您想访问这些属性,您已经知道您的 Animals[0]Carnivore 还是 Herbivore:

private void cboAnimals_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
    if(animalFactory.Animals[0] is Carnivore carnivore)
    {
        Debug.WriteLine(carnivore.MeatFood);
    }
    else if (animalFactory.Animals[0] is Herbivore herbivore)
    {
        Debug.WriteLine(herbivore.GreenFood);
    }
 }

但是此时您可能意识到您不会通过此方法获得好的代码。这表明您的 class 结构不是最好的。事实上,为什么你有两个属性 MeatFoodGreenFood?它们之间有什么区别?两者都是双重类型。因此,也许你应该在你的 BaseAnimal class 中有一个 Food 属性 并通过枚举来区分食物的类型:

public enum FoodType
{
   Meat,
   Green
}

public abstract class BaseAnimal
{
   public string? Species { get; set; }
   public double Price { get; set; }
   public double Food { get; set;}
   public abstract FoodType FoodType {get;}
}

public abstract class Carnivore : BaseAnimal
{
    public override FoodType FoodType{ get { return FoodType.Meat; } }
}

public abstract class Herbivore : BaseAnimal
{
   public override FoodType FoodType{ get { return FoodType.Green; } }
}

最简单的可能是检查类型:

var foodAmount = animalFactory.Animals[0] switch {
     Carnivore c => c.MeatFood ,
    Herbivore h => h.GreenFood ,
    _ => throw new InvalidOperationException()
};

但这是编程入门中讲授的继承的典型例子。作为一般规则,如果您需要检查类型,您可能没有充分考虑您的类型系统。继承的思想是所有具体实现都应该委托给实现,所以对象的用户不需要知道具体类型。

我很难提供具体的建议,因为这个例子太人为化了,但我建议阅读 Eric Lippert 的系列文章 Wizards and Warriors 以很好地介绍继承,以及人们经常做错的地方。

问题是动物数组是 BaseAnimal 类型。你需要将它拆箱(即将它转换为它的子类型),你还需要一些逻辑来检查类型,所以像

if (obj is Herbivore) 
{
   var herb = (Herbivore)obj;
   herb.Greenfood= 123m;
}

看起来很乱,但在某些时候你需要得到具体的类型。

你应该做的是使用 Composition。

public interface IFood {

   public double Food { get; set; }
}

public class Herbivore {

   public double Food { get; set; }
}

public class Carnivore {

   public double Food { get; set; }
}

public interface IAnimal {
   string? Species { get; set; }
   double Price { get; set; }
}

public class Animal {
   string? Species { get; set; }
   double Price { get; set; }
}

public class Ape {
   private IAnimal _animal;
   private IFood _food;

   public Ape(IAnimal animal, IFood food) {
      _animal = animal;
      _food = food;
   }
}



public void main(string[] args) {

      IFood herbivore = new Herbivore(){Food = 10};
      IAnimal ape = new Animal(){
         Price = 1000;
         Species = "Ape";
      };
      Ape ape = new Ape(ape, herbivore);
   }

Now you can compose any class of type animal you want, you can reuse the implementation of IAnimal and IFood on other types, make new ones? Like Omnivore?

您可以实现不同的物种和/或“价格”并将其加载到任何新的 class 中,例如您想要的“狗”或“猫”,并使用您的接口组合该对象。因此不需要重写你的测试,你不需要有多个实现 say ... “Speak” on dogs that all do a "bark" you can have an class that does "bark" and then把它编成所有像狗一样的动物。

想想现在实现像“消费”这样适用于所有食草动物、杂食动物或食肉动物的行为是多么容易?

您只需编写 3 个单元测试,每个单元测试一个,而不是您最终创建的每种动物类型一个,因为它们存在依赖性分离。

并且 Consume 可以处理食物类型的任何参数,因此您甚至不必关心是否找到了定义食物的新方法,例如 rock-eater 或其他东西:)

或者如果您必须将植物描述为“消耗”的东西并且您需要阳光作为食物来源?超级简单,您也不需要为此编写 x-amount 工厂类型单元测试。

组合优于继承是迄今为止在 OOP 中实现代码的更好选择。