【问题标题】:Need some help understanding inheritance需要一些帮助来理解继承
【发布时间】:2012-02-17 16:32:54
【问题描述】:

有一个任务是编写一个注册动物的程序,目标是熟悉继承、多态性等。

让我烦恼的一件事是,无论我读了多少关于它的内容都似乎毫无意义。

我创建了我的主类动物,其中包含一些适用于所有动物的通用字段,比如名称、年龄和物种。

到目前为止,所有动物都有这个信息,但每只动物都有一个独特的领域,所以我将我的猫创建为公共类猫:动物并给猫提供领域的牙齿。

现在我想制作一种新动物,它是一只猫,我从几个列表框中获取数据,所以我需要一个构造函数来获取这些参数,而这是我没有得到的,我是否必须在每个子类中声明它们也一样?

我知道我的动物应该有 3 个来自动物类的参数加上另一个来自猫类的参数,所以新猫应该接受(名字、年龄、物种、牙齿),但似乎我必须告诉猫中的构造函数班级接受所有这些,我的问题是,动物班有什么目的?如果我仍然需要在所有子类中编写代码,为什么要有基类?可能我没听懂,但我越读越困惑。

【问题讨论】:

  • 您对自己的示例感到困惑。例如 - 只有猫有牙齿,还是所有动物都有?为了争论,让我们说所有动物都这样做(一个基本正确的陈述) - 那么牙齿不应该成为你的基类的一部分吗?
  • 这只是一个例子,我将有多个类别和物种,每个物种都有一个独特的领域,但让我们将其更改为胡须:p。
  • @user1083543:这似乎是某种家庭作业/课堂作业。出于这个原因,继承的许多优点可能不会让你利用这个特定的任务。但是赋值的重点是让你熟悉继承,这样当你真正需要它时,你就可以使用它。

标签: c# class inheritance abstract


【解决方案1】:

正如 Sergey 所说,这不仅仅是关于构造函数。它使您不必一遍又一遍地初始化相同的字段。例如,

没有继承

class Cat
{
    float height;
    float weight;
    float energy;
    string breed;

    int somethingSpecificToCat;

    public Cat()
    {
        //your constructor. initialize all fields
    }

    public Eat()
    {
        energy++;
        weight++;
    }

    public Attack()
    {
        energy--;
        weight--;
    }   

}

class Dog
{
    float height;
    float weight;
    float energy;
    string breed;

    int somethingSpecificToDog;

    public Dog()
    {
        //your constructor. initialize all fields
    }

    public Eat()
    {
        energy++;
        weight++;
    }

    public Attack()
    {
        energy--;
        weight--;
    }   

}

继承

所有动物共有的东西都被移到了基类中。这样,当您想设置新动物时,无需再次输入。

abstract class Animal
{
    float height;
    float weight;
    float energy;
    string breed;

    public Eat()
    {
        energy++;
        weight++;
    }

    public Attack()
    {
        energy--;
        weight--;
    }   
}
class Cat : Animal
{   
    int somethingSpecificToCat;

    public Cat()
    {
        //your constructor. initialize all fields
    }   
}

class Dog : Animal
{   
    int somethingSpecificToDog;

    public Dog()
    {
        //your constructor. initialize all fields
    }   
}

另一个优点是,如果你想用唯一的 ID 标记每只动物,你不需要在每个构造函数中包含它并保留最后使用的 ID 的全局变量。您可以在 Animal 构造函数中轻松做到这一点,因为每次实例化派生类时都会调用它。

示例

abstract class Animal
{
    static int sID = 0;

    float height;
    float weight;
    int id;

    public Animal()
    {
        id = ++sID;
    }
}

现在当你这样做了;

Dog lassie = new Dog();  //gets ID = 1
Cat garfield = new Cat(); // gets ID = 2

如果您想要一份“农场”中所有动物的列表,

没有继承

List<Cat> cats = new List<Cat>();   //list of all cats
List<Dog> dogs = new List<Dog>(); //list of all dogs
...etc

带继承

List<Animal> animals = new List<Animal>();  //maintain a single list with all animals
animals.Add(lassie as Animal);
animals.Add(garfield as Animal);

这样,如果您想查看是否有一个名为 Pluto 的动物,您只需要遍历单个列表(动物)而不是多个列表(猫、狗、猪等)

编辑以回应您的评论

您不需要实例化 Animal。您只需创建您想要的任何 Animal 的对象。事实上,由于 Animal 永远不会是泛型 Animal,您可以将 Animal 创建为抽象类。

abstract class Animal
{
    float height;
    float weight;
    float energy;
    string breed;

    public Eat()
    {
        energy++;
        weight++;
    }

    public Attack()
    {
        energy--;
        weight--;
    }   
}
class Cat : Animal
{   
    int somethingSpecificToCat;

    public Cat()
    {
        //your constructor. initialize all fields
    }   
}

class Dog : Animal
{   
    int somethingSpecificToDog;

    public Dog()
    {
        //your constructor. initialize all fields
    }   
}

Cat garfield = new Cat();
garfield.height = 24.5;
garfield.weight = 999; //he's a fat cat
//as you can see, you just instantiate the object garfield
//and instantly have access to all members of Animal

Animal jerry = new Animal(); //throws error
//you cannot create an object of type Animal
//since Animal is an abstract class. In this example
//the right way would be to create a class Mouse deriving from animal and then doing

Mouse jerry = new Mouse();

编辑您的评论

如果您将其存储在动物列表中,您仍然可以访问所有字段。您只需将其转换回原来的类型即可。

List<Animal> animals = new List<Animal>();
animals.Add(garfield as Animal);
animals.Add(lassie as Animal);

//if you do not cast, you cannot access fields that were specific to the derived class.
Console.WriteLine(animals[0].height);   //this is valid. Prints Garfield's height
Console.WriteLine(animals[0].somethingSpecificToCat); //invalid since you haven't casted
Console.WriteLine((animals[0] as Cat).somethingSpecificToCat); //now it is valid

//if you want to do it in a loop

foreach(Animal animal in animals)
{
    //GetType() returns the derived class that the particular animal was casted FROM earlier

    if(animal is Cat)
    {
        //the animal is a cat
        Cat garfield = animal as Cat;
        garfield.height;
        garfield.somethingSpecificToCat;
    }
    else if (animal is Dog)
    {
        //animal is a dog
        Dog lassie = animal as Dog;
        lassie.height;
        lassie.somethingSpecificToDog;
    }   
}

【讨论】:

  • 那么接下来的问题:p。我的动物类应该从我的表单中获取它的值,但我该怎么称呼它?首先用参数实例化一个新的动物,然后调用创建一个新的猫?
  • @user1083543:你不需要实例化动物。您只需实例化一只猫或一只狗。事实上,如果您的动物将始终是特定类型的动物而不仅仅是通用动物,您应该在基类 Animal 上使用关键字 abstract。这可确保您无法创建 Animal 类型的对象。请参阅我正在对答案进行的编辑以更好地解释。
  • 感谢一百万如此清晰的解释,非常感谢。
  • 好吧,如果有人还在检查这个,如果我发现一个 List 不只保存动物类中定义的字段吗?所以我的子类的特定字段没有保存在里面?
【解决方案2】:

您可能需要记住,您正在处理的示例非常简单。如果您需要一些复杂的方法来确定基类值之一,您不希望在多个类中编写/复制它,因为这将变得乏味并使代码的维护成为一场噩梦,在这些类型的情况下,声明构造函数中的一些参数变得微不足道。

【讨论】:

    【解决方案3】:

    好处是您不必在每种动物中声明年龄物种的名称。您可以为您预先制作它们。继承可以让你做的另一个重要点是。假设您想要拥有一系列动物。所以你输入类似 . Arraylist arr = 等等等等... 但这只会保存 cat 类型的对象。因此,您可以改为使用 Arraylist 之类的方法,它可以容纳所有类型的动物、猫和狗。基本上,基类的变量可以指向派生类的变量。随着事情变得复杂,这在大多数情况下都很方便。

    【讨论】:

    • 看这是我不明白的,也许是我这是一个糟糕的描述,但本质上这是我得到的任务:我从文本框或列表框中获取姓名、年龄和物种,然后我需要“制作“它的一种动物。假设我选择了猫作为物种,然后我想用猫的独特数据创建一种动物。下次我将添加一条带有其唯一数据的鱼等等,唯一不同的是每个物种的唯一数据将包含在基类动物的子类中。
    • 那么您的构造函数将只调用具有公共值的基本构造函数,而特定对象将在派生构造函数中分配值
    【解决方案4】:

    你需要告诉构造器接受参数(如果你不想要求它们),但你不需要再次实现属性:

    public class Animal
    {
        public string Name { get; set; }
    
        public Animal(string Name)
        {
            Name = name;
        }
    }
    
    public class Cat : Animal
    {
        public int Teeth { get; set; }
    
        public Cat(string name, int teeth)
        {
            Name = name; //<-- got from base
            Teeth = teeth; //<-- defined localy
        }
        //or do this
        public Cat(string name, int teeth) : base(name)
        {
            Teeth = teeth;
        }
    }
    

    您还可以执行以下操作:

    Cat cat = new Cat("cat", 12);
    Animal kitty = cat as Animal;
    

    这是有道理的,例如如果你想要像List&lt;Animal&gt; 这样的列表,你可以添加一个Cat-instance:

    List<Animal> animals = new List<Animal>();
    animals.Add(new Animal("Coco"));
    animals.Add(cat);
    
    foreach(Animal animal in animals)
    {
        Console.WriteLine(String.Format("Name: {0}", animal.Name));
        if(animal is Cat)
        {
            Console.WriteLine(String.Format("{0} is a Cat with {1} teeth.", animal.Name
                (animal as Cat).Teeth));
        }
        Console.WriteLine("============");
    }
    

    将输出:

    Name: Coco
    ============
    Name: cat
    cat is a Cat with 12 teeth.
    ============
    

    【讨论】:

      【解决方案5】:

      继承不仅仅与构造函数有关。例如,在您的基类 Animal 中,您可以声明方法 Eat(something) 或 Grow(),这对于所有后继者都是相等的。

      顺便说一句,只用三个参数调用默认的 Cat() 构造函数(因此调用基础 Animal 构造函数)然后通过设置适当的字段或属性来指定牙齿是没有问题的。

      【讨论】:

      【解决方案6】:

      我不知道以下信息是否对您有用,但我认为作为继承的使用值得一提。 继承的众多用途之一,或者更具体地说,超类是您可以将它们放在同一个集合中:

      List<Animal> animals = new List<Animal>();
      
      animals.Add(new Cat());
      animals.Add(new Dog());
      

      等等。等等

      【讨论】:

        【解决方案7】:

        别忘了你也可以将构造函数参数传递给基础构造函数。您不必在每个派生类中都初始化它们。

        例如(窃取 chrfin 的代码):

        public class Animal
        {
            public string Name { get; set; }
        }
        
        public class Cat : Animal
        {
            public int Teeth { get; set; }
        
            public Cat(string name, int teeth) : Base(name) //pass name to base constructor
            {
                Teeth = teeth;
            }
        }
        

        【讨论】:

        • 刚刚在我的答案中添加了相同的内容(没有先阅读你的):D
        【解决方案8】:

        你完全想多了。

        动物类的用途是什么?

        您的场景非常非常简单。试着这样想:特征越常见,它应该放在层次越高。为什么?只是为了避免重复和冗余。想象一下有几个额外的类:狗、马和青蛙。由于猫、狗和马是哺乳动物,您还可以创建一个定义共享哺乳动物特征的哺乳动物类。为什么?例如避免为相似的物种编写相同的构造函数、字段、方法。在您的情况下,请尝试将您的动物类视为所有动物共有的特征的存储库。

        【讨论】:

          【解决方案9】:

          是的,您必须创建构造函数,但这并不意味着继承毫无意义,尽管优先考虑组合而不是继承实际上是一种很好的设计实践,通常(并非总是)拥有一只拥有动物属性的猫比拥有一只拥有动物属性的猫更好。

          回到继承,当它真正得到回报时,因为有一些你的动物都会认为可能有的属性,腿、耳朵、头,并且通过使用继承,你不必在你的每个类中声明这些属性创建。

          还可以通过继承使用多态性,比如你有这个类(伪代码)

          public abstract class Animal()
          {
              //Some atributes
          
              //Methods
          
              makeSound();
          }
          

          那么你有

          public class Cat extends Animal
          {
             makeSound()
            { 
               System.out.println("meow");
            }
          }
          

          然后说你也为狗扩展了 Animal:

          public class Dog extends Animal
          {
             makeSound()
             {
                 System.out.println("woof")
              }
          }
          

          现在假设你有一个这样声明的数组:

          List<Animal> animals = new ArrayList<Animal>();
          animals.add(cat);
          animals.add(dog);
          

          然后说你想让每只动物发出他的声音然后你可以使用多态性,这将使每个实现都调用它的 makeSound 方法:

          for (Animals animal : animals)
          {
              animal.makeSound();
          }
          

          这将为猫打印 “喵”

          为了狗

          “汪汪”

          【讨论】:

            【解决方案10】:

            继承允许您编写在类之间共享的代码,以便您将通用功能/数据放在基类中并让其他类派生自它。使用您的示例:

            class Animal
            {
                public string Name { get; set; }
            
                public Animal(string name)
                {
                    Name = name;
                }
            
            }
            
            class Cat : Animal
            {
                // Put cat-only properties here...
            
                public Cat(string name) : base(name)
                {
                    // Set cat-specific properties here...
                }
            }
            

            您甚至不需要为每个类构造函数提供相同数量的参数 - 如果一只猫没有名称(对于一个人为的示例),只需创建一个不带参数的 Cat 构造函数并传入一些东西适用于基础构造函数。这使您可以控制所有动物类的设置方式。

            【讨论】:

              【解决方案11】:

              您会感到困惑,因为您只考虑构造函数。事实正如msdn 中所解释的那样,“构造函数和析构函数不被继承”。因此,当您继承一个类时,基类构造函数不适用于派生类。派生类必须提到它自己的一组构造函数/析构函数。要了解为什么会这样,您可以查看这里:Why are constructors not inherited?

              现在来回答您的问题,是的,您必须在 cat 类中添加一个构造函数来接受所有四个参数。但是您不必在 cat 类中再次实现这 3 个字段。您的动物类的所有公共受保护和内部字段和方法仍然可供您的猫类使用,您不必在派生类中重新实现它们。这就是您的基类为派生类提供服务的方式。

              【讨论】:

              • 但是当动物类的参数是从表单传递时,它是如何工作的呢?我想创建一只猫,它应该从动物继承字段,但动物类还没有值,因为它们是从表单传递的?那么如果我想创建一只猫而不是动物,那么动物类如何知道我的表单中的值是什么?
              • 在继承中,父类将不知道派生类。所以动物类不会知道猫或狗类。你也不能指望 c# 决定它应该实例化哪个类。根据表格中选择的值,您必须编写一个程序,该程序将创建一个适当类的对象。然后将表单中的值分配给对象的适当字段。这必须由您明确完成。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-02-10
              • 2012-02-02
              • 2016-02-10
              • 2022-09-24
              • 2020-08-06
              • 2012-01-05
              • 1970-01-01
              相关资源
              最近更新 更多