• C#基础--继承与多态


    面向对象三大特性:封装,继承与多态,这里讲一下后两个特性。

    继承

    继承:指一个对象延续另一个对象的特征(属性和方法),并且有自己的个性特征(属性和方法)。

    必要性:代码复用,避免重复;一处更新,处处更新。与封装不同的是,封装主要是封装一个方法、一个类,使用时直接调用不能更改;继承主要讲需要的属性和方法(主要是方法)进行“封装”,且要使用时还可以继续扩展自己的特性(继续增加、修改方法--方法重写)。

    使用广泛:C#里,Object类是所有类的基类,winform里面所有控件都继承于Control类。

    父类与子类:当A继承于B时,A是子类(或叫派生类),B是父类(或叫基类或超类)。

    传递性:A→B,B→C,C具有A的特性。

    单根性:一个子类只能有一个父类,一个父类可以有若干子类。

    protected:只有父类与子类才能访问。

    sealed:密封类,不允许有子类,有利于保护知识产权。

    父类与子类关系:父类完全包含于子类,子类完全包含父类:

    举例说明一下继承的好处

    需求说明:

    设计玩具猫、玩具狗相关程序,要求:

    属性:姓名,自身颜色,自己类别,喜好食物

    动作:自我介绍,跳舞,赛跑

    常规实现:

    Models--Cat

     1 namespace InheritanceTest
     2 {
     3     class Cat
     4     {
     5         public string Name { get; set; }
     6         public string Color { get; set; }
     7         public string Kind { get; set; }
     8         public string Favorite { get; set; }
     9 
    10         public void Introduce()
    11         {
    12             Console.WriteLine("Hi, My name is{0}, I'm a {1}, I'm in {2}, and I like eat {3} very much! ",Name,Kind, Color,Favorite);
    13         }
    14 
    15         public void Dancing()
    16         {
    17             Console.WriteLine("Now I dancing for you!");
    18         }
    19 
    20     }
    21 }
    View Code

    Models--Dog

     1 namespace InheritanceTest
     2 {
     3     class Dog
     4     {
     5         public string Name { get; set; }
     6         public string Color { get; set; }
     7         public string Kind { get; set; }
     8         public string Favorite { get; set; }
     9 
    10         public void Introduce()
    11         {
    12             Console.WriteLine("Hi, My name is{0}, I'm a {1}, I'm in {2}, and I like eat {3} very much! ", Name, Kind, Color, Favorite);
    13         }
    14 
    15         public void Running()
    16         {
    17             Console.WriteLine("I can run very fast!");
    18         }
    19     }
    20 }
    View Code

    Program

     1 namespace InheritanceTest
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             Cat cat = new Cat()
     8             {
     9                 Name="kitty",
    10                 Kind="cat",
    11                 Color="white",
    12                 Favorite="fish"
    13             };
    14             cat.Introduce();
    15             cat.Dancing();
    16 
    17             Console.WriteLine();
    18 
    19             Dog dog = new Dog()
    20             {
    21                 Name = "David",
    22                 Kind = "dog",
    23                 Color = "brown",
    24                 Favorite = "bone"
    25             };
    26             dog.Introduce();
    27             dog.Running();
    28 
    29             Console.ReadLine();
    30         }
    31     }
    32 }
    View Code

    结果:

    问题提出:

    出现代码重复:

    使用继承来解决这个问题:

    增加父类--Animal来存放重复的代码

     1 class Animal
     2     {
     3         public string Name { get; set; }
     4         public string Color { get; set; }
     5         public string Kind { get; set; }
     6         public string Favorite { get; set; }
     7 
     8         public void Introduce()
     9         {
    10             Console.WriteLine("Hi, My name is{0}, I'm a {1}, I'm in {2}, and I like eat {3} very much! ", Name, Kind, Color, Favorite);
    11         }
    12     }
    View Code

    改一下Dog、Cat:

     1 class Dog:Animal
     2     {
     3         public void Running()
     4         {
     5             Console.WriteLine("I can run very fast!");
     6         }
     7     }
     8 
     9 //要写在各自的类里面,这里为了省事放在一起了
    10 
    11  class Cat:Animal
    12     {
    13         public void Dancing()
    14         {
    15             Console.WriteLine("Now I dancing for you!");
    16         }
    17     }
    View Code

    Program里面不需要修改,我们可以一样得到上面的结果,这就是使用继承的好处。

    继承使用的步骤与特点:

    抽象公共部分,放到特定类,即父类;

    其他类继承父类,即可拥有父类的特征(属性和方法);

    在子类中再添加自己的特征(属性和方法)。

    继承中的构造函数

    刚刚是使用对象初始化器来实现属性的赋值的,下面使用构造函数来赋值的例子,介绍this、base关键字的使用。

    Models--Animal、Cat、Dog添加构造函数(注意:Animal必须要具有无参构造函数)

     1 //Animal
     2 public Animal() { }
     3 public Animal(string name,string color,string kind)
     4 {
     5       this.Name = name;
     6       this.Color = color;
     7       this.Kind = kind;            
     8 }
     9 
    10 //Cat
    11 public Cat(string name, string color, string kind, string favorite)
    12 {
    13       this.Name = name;
    14       this.Color = color;
    15       this.Kind = kind;
    16       this.Favorite = favorite;
    17 }
    18 
    19 //Dog
    20 public Dog(string name, string color, string kind, string favorite)
    21 {
    22       this.Name = name;
    23       this.Color = color;
    24       this.Kind = kind;
    25       this.Favorite = favorite;
    26 }
    View Code

    Program改写:

     1 static void Main(string[] args)
     2         {
     3             Cat cat = new Cat("kitty", "cat", "white", "fish");                           
     4             cat.Introduce();
     5             cat.Dancing();
     6 
     7             Console.WriteLine();
     8 
     9             Dog dog = new Dog("David", "dog", "brown", "bone");          
    10             dog.Introduce();
    11             dog.Running();
    12 
    13             Console.ReadLine();
    14         }
    View Code

    再次出现代码重复:

    使用base关键字来优化:

    优化Models--Dog、Cat构造函数

    //Dog
    public Dog(string name,  string kind, string color, string favorite):base(name, color, kind)
    {            
         this.Favorite = favorite;
    }
    
    //Cat
    public Cat(string name, string kind, string color, string favorite):base(name, color, kind)
    {
          this.Favorite = favorite;
    }
    View Code

    这里使用了base关键字,该关键字除了可以调用父类的构造方法,还可以调用父类的属性和方法,使用关键字base能将逻辑变得清晰,this关键字则是表示自己类里面的属性和方法,与base做区分。

    protected关键字限制了父类的某个成员只能被其子类访问,但是如果在父类里面使用public去调用protected,依旧是可以访问带protected的成员,但是给带protected的属性赋值只能通过构造函数的方式。

    继续深入:

    我们回头看看会发现,刚刚我们解决的是属性代码重复的问题,如果现在方法代码也重复怎么办?先举例说明!

    我们给Cat、Dog添加方法:

    //Dog
    public void Have()
    {
            Console.WriteLine("I'm David, I want to eat bone please…");
    }
    
    //Cat
    public void Have()
    {
            Console.WriteLine("I'm kitty, I want to eat fish please…");
    }
    View Code

    各自单独调用自然没问题,但是如果统一放到list里面的时候,就麻烦了:

    Program:

     1 static void Main(string[] args)
     2 {            
     3        Cat cat = new Cat("kitty", "cat", "white", "fish");
     4        cat.Introduce();
     5        cat.Dancing();
     6 
     7         Console.WriteLine();
     8 
     9          Dog dog = new Dog("David", "dog", "brown", "bone");
    10          dog.Introduce();
    11          dog.Running();
    12 
    13          Console.WriteLine();
    14 
    15          List<Animal> list = new List<Animal>();
    16          list.Add(cat);
    17          list.Add(dog);
    18          foreach (var item in list)
    19          {
    20              if (item is Cat)
    21              {
    22                  Cat c = item as Cat;
    23                  c.Have();
    24              }
    25              else if (item is Dog)
    26              {
    27                  Dog d = item as Dog;
    28                  d.Have();
    29              }
    30          }
    31          Console.ReadLine();
    32      }
    33 }
    View Code

    先看一下结果:

    先介绍几个关键字:

    is : 检查对象是否与指定类型兼容,不兼容则返回false;as:用于在兼容的引用类型之间执行转换,失败则返回null 。

    父类是完全包含于子类的,因此用父类创建的list,可以放子类类型成员进去,但放进去后会被转换为父类类型成员,因此在取出时需要再进行转换。

    上面Program代码里面有几个问题:

    第一,每次在遍历List的时候都需要判断成员的类型;

    第二,都需要进行一次转换才能去调用相应的Have方法。

    而且这不符合“开-闭原则”---开发扩展,封闭修改。就是应该要尽可能少的去修改源代码,这里很明显,当子类再增加的时候,需要再去加判断才行。

    使用抽象方法进行优化

    这里使用抽象方法对以上问题进行优化,关键字:abstract, override。

    抽象类:使用abstract修饰的类,抽象类可以没有抽象方法,但抽象方法不能没有抽象类。抽象类不能创建实例对象。Animal a=new Animal();是错的,Animal c=new Cat();是对的。抽象类不能是静态的或密封的。

    抽象方法:即在父类定义一个方法,但不去实现,在子类实现或重写(若在子类重写,需要在子类的子类实现,以此类推…)。定义抽象方法使用关键字abstract,当一个类中有一个抽象方法时,需要在类的前面也加上abstract关键字;子类在方法重写时加上override关键字。

    下面用代码演示:

    Models--Animal

     1 abstract class Animal
     2     {
     3         public Animal() { }       
     4         public Animal(string name,string color,string kind)
     5         {
     6             this.Name = name;
     7             this.Color = color;
     8             this.Kind = kind;            
     9         }
    10         public string Name { get; set; }
    11         public string Color { get; set; }
    12         public string Kind { get; set; }
    13         public string Favorite { get; set; }
    14 
    15         public void Introduce()
    16         {
    17             Console.WriteLine("Hi, My name is {0}, I'm a {1}, I'm in {2}, and I like eat {3} very much! ", Name, Kind, Color, Favorite);
    18         }
    19 
    20         public abstract void Have();
    21     }
    View Code

    Models--Dog、Cat

    //Dog
    public override void Have()
    {
          Console.WriteLine("I'm David, I want to eat bone please…");
    }
    
    //Cat
    public override void Have()
    {
           Console.WriteLine("I'm kitty, I want to eat fish please…");
    }
    View Code

    Program只需要修改foreach循环:

    foreach (var item in list)
    {
           item.Have();
    }
    View Code

    不再需要判断、转换,虚拟机会在运行时自动判断当前成员类型,再去调用相应子类的重写方法。

    这里面我们在父类定义Have方法的时候使用的是抽象方法---abstract,在父类是不能对抽象方法进行编写的;还有一种可能,就是我们可以在父类对该方法进行实现,子类可以自行选择是重写该方法,还是直接使用父类的默认方法,这加强了灵活性,这就是虚方法---virtual,单纯使用虚方法的时候该类不需要再写上abstract(测试环境:VS2015,.Net 4.5),但是如果该类里面有其他抽象方法还是要写abstract关键字的。

    虚方法使用很广泛,系统自带的虚方法比如:Equals,ToString等。Equals()默认是对两个值的引用地址进行比较,如果不一致则返回false;如果我们想让他对引用类型的变量的值进行比较,就应该重写该方法。比如:

    重写Equals()

    public override bool Equals(object obj)
    {
          Cat objCat = obj as Cat;
          if (objCat.Name == this.Name && objCat.Kind == this.Kind && objCat.Color == this.Color && objCat.Favorite == this.Favorite)
                return true;
           else
                return false;
    }
    View Code

    这样去创建两个Cat对象,就可以对他们的值进行对比了。

    同理可以去改写其他的系统方法。

    补充一下:虚方法是通过override去对父类方法重写实现,实际上我们还是可以通过base.虚方法()这样的方式去调用父类的方法的;这里介绍一个方法覆盖,使用new关键字,比如上面父类Animal里面有一个Introduce方法:

    public void Introduce()
    {
         Console.WriteLine("Hi, My name is {0}, I'm a {1}, I'm in {2}, and I like eat {3} very much! ", Name, Kind, Color, Favorite);
    }

    我们在子类去进行方法覆盖,在子类Cat里进行覆盖:

    public new void Introduce()
    {
         Console.WriteLine("I think I should be different from that dog…");
    }

    当Cat对象再次去调用Introduce这个方法时,只能使用新方法,不能再去实现父类方法了。

    多态

    其实我们在上面已经用到了多态这个特性了。

    在上面我们通过抽象类抽象方法(或虚方法)实现了不同的对象去调用同样一个方法时,产生不同的行为,这就是“继承多态”,就是多态的一种,还有一个就是“接口多态”,与此类似,不过是通过接口实现的而不是继承。

    多态概念:不同对象,接受相同的信息,产生不同的行为,称为多态。多态有虚拟机自行决定,它会根据子类的原始类型,自动调用该对象使用override重写后的方法,利于程序扩展。

    使用继承实现多态的要求:

    1. 父类必须有抽象方法或虚方法;
    2. 子类必须重写父类的抽象方法或虚方法;
    3. 子类必须转换为父类类型去使用。

     可以将父类类型作为方法参数,但是传递的是子类类型,这就是里氏替换原则(LSP)

    1. 子类对象能够替换其父类;
    2. 父类对象不能够替换其子类;
    3. 父类的方法都要在子类中实现或重写。

     

    面向对象三大特性总结:

    1. 封装:隐藏内部实行,稳定外部接口                  →系统安全性
    2. 继承:子类继承父类成员,实现代码复用                →开发和维护效率
    3. 多态:不同子类,对同一消息,作出不同反映          →系统扩展性
  • 相关阅读:
    软工试水日报-纯js原生简易动态表格 3/15
    软工试水日报-Echarts动态柱形图 3/14
    大二下学期第一次结对作业(第一阶段)
    大二下学期之第一次结对作业(第一阶段)
    大二下每周总结
    大二下学期之第一次结对作业(第一阶段)
    大二下学期第一次结对作业(第一阶段)
    大二下学期第一次结对作业(第一阶段)
    大二下学期每日总结之第一次个人作业(第三阶段)
    大二下学期每日总结之第一次个人作业(第三阶段)
  • 原文地址:https://www.cnblogs.com/EasonDongH/p/8094284.html
Copyright © 2020-2023  润新知