多态:虚方法、抽象类、接口。
一、虚方法:virtual和override
场景是:每个国家的人都自报家门,然后说出自己的姓名;
先定义基类以及子类。
class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Person(string name)
{
this.Name = name;
}
public void SayHello()
{
Console.WriteLine("我是人类。");
}
}
class Chinese:Person
{
public Chinese(string name) : base(name)
{
}
public void SayHello()
{
Console.WriteLine("我是中国人,我叫{0}。", this.Name);
}
}
class Japanese:Person
{
public Japanese(string name):base(name)
{
}
public void SayHello()
{
Console.WriteLine("我是日本人,我叫{0}。", this.Name);
}
}
class American:Person
{
public American(string name):base(name)
{
}
public void SayHello()
{
Console.WriteLine("我是美国人,我叫{0}。", this.Name);
}
}
我们在Main中通过遍历的方式实现:
Chinese cn1 = new Chinese("张三");
Chinese cn2 = new Chinese("李四");
Japanese jp1 = new Japanese("松下君");
Japanese jp2 = new Japanese("井边君");
American am1 = new American("Tom");
American am2 = new American("Mary");
Person[] pers = { cn1, cn2, jp1, jp2, am1, am2 };
for (int i = 0; i < pers.Length; i++)
{
if (pers[i] is Chinese)
{
((Chinese)pers[i]).SayHello();
}
else if (pers[i] is Japanese)
{
((Japanese)pers[i]).SayHello();
}
else
{
((American)pers[i]).SayHello();
}
}
需要强制类型转换(类型)变量名,然后再访问变换类型之后自己对应的方法。
假设有80个国家,那么我们的判断需要写八十遍,一百个国家,就要重复判断一百遍。
我们希望实现:
pers[i].SayHello()
每个元素自己去输出自己所代表的子类的情况。不用人为再去判断类型。
Chinese cn1 = new Chinese("张三");
Chinese cn2 = new Chinese("李四");
Japanese jp1 = new Japanese("松下君");
Japanese jp2 = new Japanese("井边君");
American am1 = new American("Tom");
American am2 = new American("Mary");
Person[] pers = { cn1, cn2, jp1, jp2, am1, am2 };
for (int i = 0; i < pers.Length; i++)
{
pers[i].SayHello();
}
但是只会调用基类的方法。因为形式上就是调用的基类方法。
我们看vs的提示:
如果想要有意隐藏基类方法,可以在子类方法前加上new。但是效果仍然不符合。
为此:新的方法:虚方法:
实现方式:virtual和override。
用virtual将父类的方法标记为虚方法。(在返回类型之前。这个函数可以被子类重写。)
用override的标记子类重写的同名方法。
class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Person(string name)
{
this.Name = name;
}
// 构造虚方法:将方法用virtual来修饰。
public virtual void SayHello()
{
Console.WriteLine("我是人类。");
}
}
class Chinese:Person
{
public Chinese(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是中国人,我叫{0}。", this.Name);
}
}
class Japanese:Person
{
public Japanese(string name):base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是日本人,我叫{0}。", this.Name);
}
}
class American:Person
{
public American(string name):base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是美国人,我叫{0}。", this.Name);
}
}
此时就可以满足我们理想的预期。而且,再加再多人的,也不用害怕。无序再去判断类型就可以直接如期输出。
这种多态是一种动态多态:相同案例见C++代码例程:
《C++类与对象笔记十四:多态一:多态概念、虚函数、动态多态、静态多态、重写虚函数、里氏转换法》
《C++类与对象笔记十四:多态二:多态的底层原理(C++程序员Must掌握)》
二、抽象类:abstract
场景是:狗狗汪汪叫,猫咪喵喵叫。
我们需要抽象出一个基类:比如动物类:Animal。这个类中,有一个方法叫:Bark()。这个方法应该是所有动物共同的叫法,但是,这个动作无法完成,无法实现。因此我们抽象这样一个动作。抽象这样一个方法,这样的方法应当存在于抽象的类中。所以对方法和类添加关键字:abstract。
抽象函数,不允许有方法体。
这个方法存在的意义就是让子类重写。子类重写进而实现多态。
public abstract class Animal
{
public abstract void Bark();
}
public class Dog:Animal
{
public override void Bark()
{
Console.WriteLine("狗汪汪叫。");
}
}
public class Cat:Animal
{
public override void Bark()
{
Console.WriteLine("猫喵喵喵叫。") ;
}
}
如何调用实现呢?
// 抽象类不能实现:只能声明父类的类型去指向子类的对象。
Animal a = new Dog();
a.Bark();
Animal b = new Cat();
b.Bark();
注意事项:
如果这个方法可以实现,可以写出来,那么就应该用虚方法,
无法写出,或者无意义,就用抽象类。
抽象类的特点:
1、抽象成员必须标记为abstract,并且不能有任何实现。
2、抽象成员必须在抽象类内。
3、抽象类不能被实例化。
4、子类继承抽象类后,必须把父类中的所有抽象成员都重写。(除非子类也是一个抽象类,则可以不重写。)
5、抽象成员的访问修饰符不能是private。
6、在抽象类中可以包含实例成员。并且抽象类的实例成员可以不被子类实现。
7、抽象类是有构造函数的。虽然不能被实例化。
8、如果父类的抽象方法中有参数,那么,继承这个抽象父类的子类在重写父类的方法的时候必须传入对应的参数;如果抽象父类的抽象方法中有返回值,那么子类在重写这个抽象方法的时候,也必须要传入返回值。
例子场景:使用多态求矩形的面积和周长,以及圆形的面积和周长。
思考:矩形和圆形可以抽象出一个父类:Shape。这个类中要包含两个方法:一个是求面积,一个是求周长。这两个类在父类中无法实现。所以选择用抽象方法。因此该类是抽象类。
public abstract class Shape
{
public abstract double GetArea();
public abstract double GetPerimeter();
}
public class Circle:Shape
{
private double _r;
public double R
{
get { return _r; }
set { _r = value; }
}
public Circle(double r)
{
this.R = r;
}
public override double GetArea()
{
return Math.PI * this.R * this.R;
}
public override double GetPerimeter()
{
return 2 * Math.PI * this.R;
}
}
public class Square:Shape
{
private double _height;
public double Height
{
get { return _height; }
set { _height = value; }
}
private double _width;
public double Width
{
get { return _width; }
set { _width = value; }
}
public Square(double height, double width)
{
this.Height = height;
this.Width = width;
}
public override double GetArea()
{
return this.Height * this.Width;
}
public override double GetPerimeter()
{
return (this.Width + this.Height) * 2;
}
}
三、接口:interface
C#的父类要求:单根性,即只能有一个父类。
场景1:比如:胡萝卜,可以属于蔬菜类,也属于可食用类,还属于富含VC类食物。我们如何继承多个类的特性呢?通过接口的方式。
场景2:飞机、麻雀、大雁、火箭,这几个类别无法抽象出共同的父类,但可以找出它们共同的行为、共同的能力:飞行。可以用接口来实现。
语法:[public] interface I...able
以I开头,以able结尾,表示具备一种能力。
一个类,继承一个接口,必须要实现接口中的成员。
因此,给我们的提示是:
接口必须简洁、通用性强。
接口其实就是一种规范、一种能力:
- 规范:USB接口就是一种统一标准,一种规范,所有的厂家都要按照此生产加工。
- 能力:实现某一个功能,表示一种能力。
语法:
1、接口中的成员,继承类必须全部实现。(而非重写,故无override)
2、接口中的成员,不允许添加访问修饰符。默认就是public。
3、接口中的成员,不允许有实现(即:函数方法不允许有方法体。属性中方法也不能写方法体。)。"光说不做"。
4、接口中的成员,不能包含字段。(字段是存数据的,接口不需要存数据。)
5、可以有属性,为自动属性。属性其实也就是提供了两个方法(get和set)。所以可以说接口中只可以有方法。
6、接口不可以被实例化。(因为接口成员不允许有实现,所以实例化无意义。)
但是因为可以被继承,如果想实现多态的话,可以声明一个接口指向一个子类对象。
IFlyable fly = new Bird(); //IFlyable fly = new Plane(); fly.Fly();
7、不能实例化的创建对象,所以用构造函数也没用。所以就没有构造函数。
8、接口与接口之间可以继承,而且可以多继承。(类与类之间不可以多继承。单根性。)
9、接口只能继承于接口,接口不能继承于一个类,而类可以继承接口。
10、基类必须在任何接口之前。多继承,先写类。
public interface IFlyable
{
void Fly();
}
public class Plane:IFlyable
{
public void Fly()
{
Console.WriteLine("人类飞行器。");
}
}
public class Bird:IFlyable
{
public void Fly()
{
Console.WriteLine("鸟类可以飞。");
}
}
与其说是面向对象的编程,不如说是面向接口的编程。
总结:
Q:什么时候用虚方法来实现多态?
- 可以抽象出共同的父类, 父类中可以写所有对象共有的方法,并需要创建父类的对象。
Q:什么时候用抽象类来实现多态?
- 可以抽象出共同的父类, 父类中写所有对象共有的方法,但是不知道如何写这个方法。
Q:什么时候用接口来实现多态?
- 无法找到共有的父类,但是这几个类别却有共同的行为或者能力。