面向对象三大特性:封装,继承与多态,这里讲一下后两个特性。
继承
继承:指一个对象延续另一个对象的特征(属性和方法),并且有自己的个性特征(属性和方法)。
必要性:代码复用,避免重复;一处更新,处处更新。与封装不同的是,封装主要是封装一个方法、一个类,使用时直接调用不能更改;继承主要讲需要的属性和方法(主要是方法)进行“封装”,且要使用时还可以继续扩展自己的特性(继续增加、修改方法--方法重写)。
使用广泛: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 }
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 }
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 }
结果:

问题提出:
出现代码重复:


使用继承来解决这个问题:
增加父类--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 }
改一下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 }
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 }
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 }
再次出现代码重复:


使用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;
}
这里使用了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…");
}
各自单独调用自然没问题,但是如果统一放到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 }
先看一下结果:

先介绍几个关键字:
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 }
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…");
}
Program只需要修改foreach循环:

foreach (var item in list)
{
item.Have();
}
不再需要判断、转换,虚拟机会在运行时自动判断当前成员类型,再去调用相应子类的重写方法。
这里面我们在父类定义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;
}
这样去创建两个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重写后的方法,利于程序扩展。
使用继承实现多态的要求:
- 父类必须有抽象方法或虚方法;
- 子类必须重写父类的抽象方法或虚方法;
- 子类必须转换为父类类型去使用。
可以将父类类型作为方法参数,但是传递的是子类类型,这就是里氏替换原则(LSP):
- 子类对象能够替换其父类;
- 父类对象不能够替换其子类;
- 父类的方法都要在子类中实现或重写。
面向对象三大特性总结:
- 封装:隐藏内部实行,稳定外部接口 →系统安全性
- 继承:子类继承父类成员,实现代码复用 →开发和维护效率
- 多态:不同子类,对同一消息,作出不同反映 →系统扩展性
来源:https://www.cnblogs.com/EasonDongH/p/8094284.html
