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. 多态:不同子类,对同一消息,作出不同反映          →系统扩展性
原文地址:https://www.cnblogs.com/EasonDongH/p/8094284.html