当我们想让一个对象,能够表现出多种状态(类型)的时候,就要用到多态。
实现多态有三种手段,下面我来分别介绍。
虚方法
1.首先,使用关键字virtual标记父类中的方法,
public virtual void SayHello()
{
Console.WriteLine("我是人类");
}
表示这个方法可以被子类重写一遍。
子类中如果想要重写父类的方法,用override关键字标记,
public override void SayHello()
{
Console.WriteLine("我是中国人,我叫{0}", this.Name);
}
2.然后创建子类对象
Chinese cn1 = new Chinese("韩梅梅");
Chinese cn2 = new Chinese("李雷");
Japanese j1 = new Japanese("树下君");
Japanese j2 = new Japanese("井边子");
Korea k1 = new Korea("金秀贤");
Korea k2 = new Korea("金贤秀");
American a1 = new American("科比布莱恩特");
American a2 = new American("奥尼尔");
子类:(每一个子类都类似,这里就不重复表示出来了)
public class Chinese : Person
{
public Chinese(string name) : base(name)
{
}
public override void SayHello()
{
Console.WriteLine("我是中国人,我叫{0}", this.Name);
}
}
3.创建一个父类的数组,用来存储子类对象
Person[] pers = { cn1, cn2, j1, j2, k1, k2, a1, a2 };
4.最后输出
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 if(pers[i] is Korea)
// {
// ((Korea)pers[i]).SayHello();
// }
// else
// {
// ((American)pers[i]).SayHello();
// }
pers[i].SayHello();
}
可以看出,如果没有使用多态,就需要写很多的if elseif判断是哪一个子类在调用方法,这样对于我们来说实在是太麻烦了。当我们用了多态之后,只需要这一行代码就能轻松的表示出所有类型。如果有新的类加入,只需要再创建对象放进数组就可以了。
练习
经理十一点打卡,员工九点打卡,程序员不打卡,使用多态
public class Employee
{
public virtual void DaKa()
{
Console.WriteLine("九点打卡");
}
}
public class Manger : Employee
{
public override void DaKa()
{
Console.WriteLine("经理十一点打卡");
}
}
public class Programmer : Employee
{
public override void DaKa()
{
Console.WriteLine("程序员不打卡");
}
}
Employee em = new Employee();
Manger ma = new Manger();
Programmer pr = new Programmer();
Employee[] emp = { em, ma, pr };
for (int i = 0; i < emp.Length; i++)
{
emp[i].DaKa();
}
Console.ReadKey();
抽象类
来看这样一个问题:狗狗会叫,猫咪也会叫,使用多态。
思路:创建一个父类Animal,让子类重写父类“叫”的方法。
但是这会出现一个问题,我们知道狗的叫声,猫的叫声,但是父类Animal怎么叫?我们并不知道是什么动物在叫。
public class Animal
{
public void Bark()
{
//?怎么叫
}
}
所以这个方法没法写出来。
那么这个时候如果想实现多态,我们可以用抽象类来解决。
我们使用abstract关键字来标记一个类是抽象类,同样用abstruct关键字来标记一个方法是抽象方法,但是这个抽象方法没有方法体。
public abstract class Animal
{
public abstract void Bark();
}
特点
1.抽象成员必须标记为abstract,并且不能有任何实现。
2.抽象成员必须在抽象类中
3.抽象类不能被实例化(因为这个类如果实例化,并不能调用到自己的任何成员,没有意义。)
4.子类继承抽象类后,必须把父类中的所有抽象成员都重写(除非子类也是一个抽象类,可以不重写)
5.抽象成员的访问修饰符不能是private(如果是private,子类就不能访问到该方法,也就不能实现)
6.在抽象类中可以包含实例成员,并且抽象类的实例成员可以不被子类实现(虽然自己用不了但是子类都可以继承)
7.抽象类是有构造函数的,虽然不能被实例化。
8.如果父类的抽象方法中有参数,那么继承这个抽象父类的子类在重写父类的方法的时候,必须传入相对应的参数。如果抽象父类的抽象方法中有返回值,那么子类在重写这个抽象方法的时候也必须有返回值。
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();
Console.ReadKey();
现在如果有更改,只需要更改new Dog()这一部分的代码就可以了。
再一次大大减少了代码的差异。
练习
使用多态求矩形的面积和周长以及圆形的面积和周长
思路:抽象出一个形状类,使用多态,让子类圆形和矩形分别重写面积周长方法
public abstruct class Shape
{
public abstruct double GetArea();
public abstract double GetPerimeter();
}
public class Circle : Shape
{
private double _r;
public doubule 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 Math.PI * this.R * 2;
}
}
public class Square : Shape
{
private double _height;
public double Herght
{
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.Height * this.Width) * 2;
}
}
Shape shape = new Square(5,6);
double area = shape.GetArea();
double perimeter = shape.GetPerimeter();
Console.WriteLine("这个形状的面积是{0},周长是{1}",area,perimeter);
Console.ReadKey();
小结
如果父类中的方法有默认的实现,并且父类需要被实例化,可以考虑将父类定义成一个普通类,用虚方法来实现多态。
如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类。
练习
用多态来实现,将移动硬盘或者U盘,或者mps插到电脑上进行读写数据
思路:
首先,我们可以把移动硬盘,MP3,U盘抽象成一个类叫做可移动存储设备。这个父类具有读写的两个方法。
但是我们不知道插在电脑上的设备具体是哪一个,所以这两个方法我们不知道该如何写。因此将父类写成一个抽象类,让子类重写父类的方法,以到达具体设备读写功能。
接着想,电脑也有两个方法,读数据,写数据。电脑怎么知道我们使用的是哪一个移动设备呢?
写一个电脑类,在电脑类中写一个返回类型为移动存储设备的字段和属性,通过传一个子类的移动设备,然后让这个传进来的子类,去进行指定的读写操作,完成多态的实现。
public abstract class MobileStorage
{
public abstract void Read();
public abstract void Wirte();
}
public class MobileDisk : MobileStorage
{
public override void Read()
{
Console.WriteLine("移动硬盘在读取数据");
}
public override void Write()
{
Console.WriteLine("移动硬盘在写入数据");
}
}
public class MP3 : MobileStorage
{
public override void Read()
{
Console.WriteLine("MP3在读取数据");
}
public override void Write()
{
Console.WriteLine("MP3在写入数据");
}
public void PlayMusic()
{
Console.WriteLine("MP3在播放音乐");
}
}
public class UDisk : MobileStorage
{
public override void Read()
{
Console.WriteLine("U盘在读取数据");
}
public override void Write()
{
Console.WriteLine("U盘在写入数据");
}
}
public class Computer
{
private MobileStorage _ms;
public MobileStorage Ms
{
get { return _ms; }
set { _ms = value; }
}
public void CpuRead()
{
Ms.Read();
}
public void CpuWrite()
{
Ms.Write();
}
}
MobileStorage ms = new UDisk();
Computer cpu = new Computer();
cpu.Ms = ms;
cpu.CpuRead();
cpu.CpuWrite();
Console.ReadKey();
重写父类ToString
ToString是父类object的虚方法,子类可重写
public class Person
{
public override string ToString()
{
return "hello world";
}
}
Person p = new Person();
Console.WriteLine(p.ToString());
Console.ReadKey();
接口
接口就是一个规范,一个能力。
[public] interface 接口名(I..able)
{
成员;
}
特点
1.一个类可以同时继承一个类并实现多个接口。如果一个子类同时继承了父类A,并实现了接口IA,那么语法上A必须在IA前面
2.不允许有访问修饰符 默认为public。不能有字段和构造函数。不能有任何实现。可以有方法、自动属性、索引器、事件。
3.实现接口的子类必须实现该接口的全部成员。
4.接口与接口之间可以继承,并且可以多继承。但是接口并不能继承一个类,类可以继承接口。
public interface M1
{
void Test1();
}
public interface M2
{
void Test2();
}
public interface M3//:Person
{
void Test3();
}
public interface SupperInterface:M1,M2,M3
{
}
public class Car : SupperInterface
{
public void Test1()
{
throw new NotImplementedException();
}
public void Test2()
{
throw new NotImplementedException();
}
public void Test3()
{
throw new NotImplementedException();
}
}
5.接口不允许实例化
显示实现接口
显示实现接口就是为了解决方法的重名问题。
IFlyable fly = new Bird();
fly.Fly();
Bird bird = new Bird();
bird.Fly();
Console.ReadKey();
public class Bird:IFlyable
{
public void Fly()
{
Console.WriteLine("鸟会飞");
}
void IFlyable.Fly()
{
Console.WriteLine("我是接口的飞");
}
}
public interface IFlyable
{
void Fly();
}
练习
麻雀会飞,鹦鹉会飞,鸵鸟不会飞,企鹅不会飞,直升机会飞,用多态实现
思路:飞机不能抽象成鸟类,但是和鸟类有相同的方法,可以使用接口
public abstract class Bird
{
public double Wings
{
get;
set;
}
public void EatAndDrink()
{
Console.WriteLine("我会吃喝");
}
//public abstract void Fly();
//public void Fly()
//{
// Console.WriteLine("会飞");
//}
}
public interface ISpeakable
{
void Speak();
}
public interface IFlyable
{
void Fly();
}
public class MaQue:Bird,IFlyable
{
public void Fly()
{
Console.WriteLine("我是麻雀我会飞");
}
}
public class YingWu : Bird,IFlyable,ISpeakable
{
public void Speak()
{
Console.WriteLine("鹦鹉会学人类说话");
}
public void Fly()
{
Console.WriteLine("我是鹦鹉我会飞");
}
}
public class TuoNiao : Bird
{
}
public class QiE : Bird
{
}
public class Plane : IFlyable
{
public void Fly()
{
Console.WriteLine("我是直升机我会飞");
}
}
IFlyable fly = new Plane();
fly.Fly();
Console.ReadKey();
总结
什么时候用虚方法来实现多态?
这个方法可以写,还需要创建这个父类的对象。
什么时候用抽象类来实现多态?
在提供这几个类中,如果说能抽象出一个父类,并且父类当中必须得写上这几个子类共有的一个方法,还不知道如何写这个方法。
什么时候用接口来实现多态?
这几个类中根本找不出父类,但都有一个共同的方法。
访问修饰符
public:公开的公共的
private:私有的,只能在当前类的内部访问
protected:受保护的,只能在当前类的内部以及该类的子类中访问
internal:只能在当前项目中访问,在同一个项目中,internal和public的权限是一样的
protected internal:
修饰类的只有public,internal
不手动加public,默认internal
可访问性不一致,子类的访问权限不能高于父类的访问权限,会暴露父类的成员
简单工厂设计模型
设计模型:设计这个项目的一种方式。
简单工厂设计模型:
在不知道用户需求的时候,工厂依然需要生产运作,那么工厂生产一个父类出来,是一定不会错的。
抽象一个父类
public abstract class NoteBook
{
public abstract void SayHello();
}
public class Lenovo : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是联想");
}
}
public class Acer : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是鸿基");
}
}
public class Dell : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是戴尔");
}
}
public class IBM : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是ibm");
}
}
Console.WriteLine("请输入您想要的笔记本品牌");
string brand = Console.ReadLine();
NoteBook nb = GetNoteBook(brand);
nb.SayHello();
Console.ReadKey();
写一个方法,根据用户需要赋值给父类
*简单工厂核心*
public static NoteBook GetNoteBook(string brand)
{
NoteBook nb = null;
switch (brand)
{
case "Lenovo":nb = new Lenovo();
break;
case "IBM":nb = new IBM();
break;
case "Dell":nb = new Dell();
break;
case "Acer":nb = new Acer();
break;
}
return nb;
}