15.1 多态简介
1)多态的概念:
让一个对象能够表现出多种的状态(类型),意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
假设有一个主人类。喂食不同的动物。不同动物对象调用方法的时候,对重复的代码频繁的修改。代码的可展拓展性,可维护性差。所以我们可以用一个Animal类作为参数,让不同的动物继承这个动物类,这就是多态。
2)实现多态的三种手段:
虚方法(virtual-override)
抽象类(abstract)
接口(interface )
3)多态的好处:
应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。(继承)
派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。(多态的真正作用)
15.2 多态--虚方法
在父类的方法前面加关键字virtual, 子类重写该方法时在方法名前面加上override(重写)关键字。虚方法可以在不同的继承类中有不同的实现,即为基类中定义的允许在派生类中重写的方法。
1)声明:
访问修饰符 virtual 函数返回类型 函数名(参数表) {函数体};
2)注意:
① 因为虚方法需要被子类调用,所以访问修饰符不能为private。
② 父类虚方法使用的什么访问修饰符,子类重写就必须用什么访问修饰符。
③ 在父类中声明的虚方法一般在子类中对其进行调用,会运用到base关键字。
3)虚方法练习:
class Person
{
public Person(string name)
{
this.Name = name;
}
private string _name;
public string Name { get => _name; set => _name = value; }
//父类方法加virtual,子类用override重写该方法,就实现了多态
public virtual void SayHello()
{
Console.WriteLine("我是父类的方法");
}
}
//学生类和教师都继承于Person:
class Student : Person
{
public Student(string name) : base(name) { }
public override void SayHello()
{
Console.WriteLine($"我叫{
this.Name}, 我是学生");
}
}
class Teacher:Person
{
public Teacher(string name) : base(name) { }
public override void SayHello()
{
Console.WriteLine($"我叫{
this.Name}, 我是老师");
}
}
//然后在Main函数中使用多态:
static void Main(string[] args)
{
Student st = new Student("学生");
Teacher th = new Teacher("老师");
Person[] p = { st, th }; //子类对象赋给父类
for(int i = 0; i < p.Length; i++)
{
p[i].SayHello();
}
Console.ReadKey();
}
输出结果:
我叫学生, 我是学生
我叫老师, 我是老师
15.3 多态--抽象类
当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法,在类前面加关键字abstract,方法前面加abstract,抽象方法不能有函数体。有大括号,里面没有内容叫空实现。抽象类不允许创建对象和接口。
1)抽象类的特点:
- 抽象类成员必须标记为abstract,并且不能有任何实现
- 抽象类成员必须在抽象类中
- 抽象类不能实例化
- 子类继承抽象类后,必须把父类中的所有抽象成员都重写(除非子类也是一个抽象类,可以不重写)
- 抽象成员的访问修饰符不能是private
- 在抽象类中可以包含实例成员,并且抽象类的实例成员可以不被子类实现
- 抽象类是有构造函数的,虽然不能被实例化
- 如果父类的抽象方法中有参数,那么,继承这个抽象类的子类在重写父类的方法时必须传入对应的参数。如果抽象父类的抽象方法中有返回值,那么子类在重写抽象方法时,也必须要传入返回值
2)抽象类示例:
abstract class Person
{
//抽象方法不能有函数体
public abstract void SayHello();
}
class Student : Person
{
public override void SayHello()
{
Console.WriteLine("我是子类Student重写的抽象方法");
}
}
class Teacher : Person
{
public override void SayHello()
{
Console.WriteLine("我是子类Teacher重写的抽象方法");
}
}
class Program
{
static void Main(string[] args)
{
List<Person> clist = new List<Person>();
Student st = new Student();
Teacher th = new Teacher();
clist.Add(st);
clist.Add(th);
foreach (Person p in clist)
{
p.SayHello();
}
Console.ReadKey();
}
}
输出结果:
我是子类Student重写的抽象方法
我是子类Teacher重写的抽象方法
3)抽象类练习:求圆和长方形的面积和周长
//父类--形状
public abstract class Shape
{
public abstract double GetArea();
public abstract double GetPrimeter();
}
//子类--圆形
public class Circle : Shape
{
private double _r;
public double R { get => _r; set => _r = value; }
//圆形的构造方法,需要传入r即半径
public Circle(double r)
{
this.R = r;
}
public override double GetArea()
{
return Math.PI * R * R;
}
public override double GetPrimeter()
{
return Math.PI * R * 2;
}
}
//子类--方形
public class Square : Shape
{
private double _height;
private double _weigh;
public double Height { get => _height; set => _height = value; }
public double Weigh { get => _weigh; set => _weigh = value; }
//构造函数,需要传入长和高
public Square(double height, double weigh)
{
this.Height = height;
this.Weigh = weigh;
}
public override double GetArea()
{
return Height * Weigh;
}
public override double GetPrimeter()
{
return (Height + Weigh) * 2;
}
}
class Program
{
static void Main(string[] args)
{
Shape p = new Circle(5);
double area = p.GetArea();
double primeter = p.GetPrimeter();
Console.WriteLine("圆的Area={0},圆的Primeter={1}", area, primeter);
Shape s = new Square(10, 20);
double s_area = s.GetArea();
double s_primeter = s.GetPrimeter();
Console.WriteLine("方形的Area={0},方形的Primeter={1}", s_area, s_primeter);
Console.ReadKey();
}
}
输出结果:
圆的Area=78.5398163397448,圆的Primeter=31.4159265358979
方形的Area=200,方形的Primeter=60
15.4 多态--接口
1)接口简介
- 接口就是为了约束方法的格式(参数和返回值类型)而存在的,接口可以实现多继承,弥补单继承的缺陷。
- 接口可以看成是一个特殊的抽象类,通过反编译看源码可知。
- 接口中方法不用访问修饰符,因为CLR会自动添加,并且不能有方法体。
2)注意:
- 如果一个类实现了某个接口,就必须得实现该接口中所有的方法
- 接口要谨慎使用,防止出现接口污染!
- 接口仅仅代表一种能力,实现该接口的类和接口没有继承关系
- 接口是用来实现的,类是用来继承的。
- 其实很多时候,看似可以不用接口,因为接口就是一个方法的约定,表明你这个类必须要有某些方法,但是不写接口也可以有这些方法,用了接口,就可以使用接口变量,统一调用,实现多态
- 接口中只能定义方法,不能定义变量。
3)抽象类和接口的区别:
- 当需要的各个对象之间存在父子类关系时,可以考虑使用抽象类,
- 当各个对象之间不存在继承关系,只是有相同的能力时,而已考虑使用接口
4)接口的通俗理解:
- 飞机会飞,鸟会飞,他们都继承了同一个接口“飞”;但是F22属于飞机抽象类,鸽子属于鸟抽象类。
- 就像铁门木门都是门(抽象类),你想要个门我给不了(不能实例化),但我可以给你个具体的铁门或木门(多态);而且只能是门,你不能说它是窗(单继承);一个门可以有锁(接口)也可以有门铃(多实现)。门(抽象类)定义了你是什么,接口(锁)规定了你能做什么。(一个接口最好只能做一件事,你不能要求锁也能发出声音吧(接口污染))
5) 练习1:
public interface IWeapon
{
void Fire();
}
class Gun : IWeapon
{
public void Fire()
{
Console.WriteLine("我是枪");
}
}
class Sword : IWeapon
{
public void Fire()
{
Console.WriteLine("我是剑");
}
}
class Tank : IWeapon
{
public void Fire()
{
Console.WriteLine("我是坦克");
}
}
class Program
{
static void Main(string[] args)
{
List<IWeapon> list = new List<IWeapon>();
Gun gun = new Gun();
Sword sw = new Sword();
Tank ta = new Tank();
list.Add(gun);
list.Add(sw);
list.Add(ta);
foreach (IWeapon p in list)
{
p.Fire();
}
Console.ReadKey();
}
}
输出结果:
我是枪
我是剑
我是坦克
6)练习2:
public interface ISwiming
{
void Swiming();
}
public class RealDusk : ISwiming
{
public void Swiming()
{
Console.WriteLine("真的鸭子用脚游泳");
}
}
public class XPDusk : ISwiming
{
public void Swiming()
{
Console.WriteLine("橡皮鸭子漂浮游泳");
}
}
public class MTDusk : ISwiming
{
public void Swiming()
{
Console.WriteLine("木头鸭子不能游泳");
}
}
class Program
{
static void Main(string[] args)
{
ISwiming iw = new RealDusk();
iw.Swiming();
Console.ReadKey();
}
}
输出结果:真的鸭子用脚游泳
15.5 多态练习
用多态来实现将移动硬盘或者U盘或者MP3查到电脑上进行读写数据。
public abstract class MobieStorage
{
public abstract void Read();
public abstract void Write();
}
public class MobieDisk : MobieStorage
{
public override void Read()
{
Console.WriteLine("这是移动硬盘的读取");
}
public override void Write()
{
Console.WriteLine("这是移动硬盘的写入");
}
}
public class MP3Disk : MobieStorage
{
public override void Read()
{
Console.WriteLine("这是MP3的读取");
}
public override void Write()
{
Console.WriteLine("这是MP3的写入");
}
public void PlayMusic()
{
Console.WriteLine("开始播放音乐");
}
}
public class UPDisk : MobieStorage
{
public override void Read()
{
Console.WriteLine("这是U盘的读取");
}
public override void Write()
{
Console.WriteLine("这是U盘的写入");
}
}
public class Computer
{
private MobieStorage ms;
public MobieStorage Ms { get => ms; set => ms = value; }
public void CpuWrite()
{
Ms.Write();
}
public void CpuRead()
{
Ms.Read();
}
}
class Program
{
static void Main(string[] args)
{
MobieStorage ms = new UPDisk();
Computer cp = new Computer();
//将ms传入字段里面
cp.Ms = ms;
cp.CpuRead();
cp.CpuWrite();
Console.ReadKey();
}
}
15.6 综合练习 超市收银系统
1)商品类
//商品父类
class ProductFather
{
string _name;
double _price;
string _id;
public string Name { get => _name; set => _name = value; }
public double Price { get => _price; set => _price = value; }
public string Id { get => _id; set => _id = value; }
public ProductFather(string name, double price, string id)
{
this.Name = name;
this.Price = price;
this.Id = id;
}
}
class Acer : ProductFather
{
public Acer(string name, double price, string id): base(name, price, id)
{
}
}
class Banana : ProductFather
{
public Banana(string name, double price, string id): base(name, price, id)
{
}
}
class SanSung : ProductFather
{
public SanSung(string name, double price, string id): base(name, price, id)
{
}
}
class JianYou : ProductFather
{
public JianYou(string name, double price, string id): base(name, price, id)
{
}
}
2)仓库类
class CanKu
{
List<List<ProductFather>> list = new List<List<ProductFather>>();//展示货物
public void ShowPros()
{
foreach (var item in list)
{
Console.WriteLine("我们仓库有{0},价格是{1}元,有{2}
个",item[0].Name,item[0].Price, item.Count);
//item:货架 item[0]:货架上第一个商品 //item
}
}
public CanKu()
{
list.Add(new List<ProductFather>());
list.Add(new List<ProductFather>());
list.Add(new List<ProductFather>());
list.Add(new List<ProductFather>());
}
//进货
public void GetPros(string type, int count)
{
for (int i = 0; i < count; i++)
{
switch (type)
{
case "Acer":
list[0].Add(new Acer("红星笔记本", 7999, Guid.NewGuid().ToString()));
break;
case "Banana":
list[1].Add(new Banana("香蕉", 15,Guid.NewGuid().ToString()));
break;
case "JianYou":
list[2].Add(new JianYou("海天酱油", 30,Guid.NewGuid().ToString()));
break;
case "SanSung":
list[3].Add(new SanSung("三星手机", 3000,Guid.NewGuid().ToString()));
break;
}
}
}
//取货
public ProductFather[] QuPros(string type, int count)
{
ProductFather[] pros = new ProductFather[count];
for (int i = 0; i < pros.Length; i++)
{
switch (type)
{
case "Acer":
//第一个[0]表示第一个货架,第二个[0]表示第一个商品
pros[i] = list[0][0];
list[0].RemoveAt(0);
break;
case "Banana":
pros[i] = list[1][0];
list[1].RemoveAt(0);
break;
case "JianYou":
pros[i] = list[2][0];
list[2].RemoveAt(0);
break;
case "SanSung":
pros[i] = list[3][0];
list[3].RemoveAt(0);
break;
}
}
return pros;
}
}
3)打折类
abstract class CalFather
{
//计算打折后的钱
public abstract double GetTotalMoney(double Money);
}
class CalNormal : CalFather
{
//返回原价
public override double GetTotalMoney(double Money)
{
return Money;
}
}
class CalRate : CalFather
{
private double _rate;
public double Rate { get => _rate; set => _rate = value; }
public CalRate(double rate)
{
this.Rate = _rate;
}
public override double GetTotalMoney(double Money)
{
return Money * this.Rate;
}
}
class CalMN : CalFather
{
private double _m;
private double _n;
public double M { get => _m; set => _m = value; }
public double N { get => _n; set => _n = value; }
public CalMN(double m, double n)
{
this.M = m;
this.N = n;
}
public override double GetTotalMoney(double Money)
{
if (Money >= this.M)
{
return Money - ((int)(Money / this.M)) * this.N;
}
else
{
return Money;
}
}
}
4)超市类
class SuperMarket
{
//创建仓库对象后,自动调用仓库中的构建函数,创建四个货架
CanKu ck = new CanKu();
//创建超市对象的时候,给仓库的货架上导入货物
public SuperMarket()
{
ck.GetPros("Acer", 1000);
ck.GetPros("Banana", 1000);
ck.GetPros("JianYou", 1000);
ck.GetPros("SanSung", 1000);
}
public void AskBuying()
{
Console.WriteLine("欢迎光临,请问您需要些什么");
Console.WriteLine("我们有 Acer、JianYou、Banana、SanSung");
string type = Console.ReadLine();
Console.WriteLine("你需要多少");
int count = Convert.ToInt32(Console.ReadLine());
//去仓库取货
ProductFather[] pros = ck.QuPros(type, count);
//计算价钱
double Money = GetMoney(pros);
Console.WriteLine("您总共需要付{0}元", Money);
Console.WriteLine("请选择你的打折方式 1--不打折 2--打九折 3--打85折 4--买300送50 5--买500送100");
string input = Console.ReadLine();
//通过简单工厂的设计模式根据用户的输入获得一个打折对象
CalFather cal = GetCal(input);
double calMoney = cal.GetTotalMoney(Money);
Console.WriteLine("打完折后应付{0}元", calMoney);
}
public CalFather GetCal(string input)
{
CalFather cal = null;
switch (input)
{
case "1":
cal = new CalNormal();
break;
case "2":
cal = new CalRate(0.9);
break;
case "3":
cal = new CalRate(0.85);
break;
case "4":
cal = new CalMN(300, 50);
break;
case "5":
cal = new CalMN(500, 100);
break;
}
return cal;
}
public double GetMoney(ProductFather[] pros)
{
double Money = 0;
for (int i = 0; i < pros.Length; i++)
{
Money += pros[i].Price;
}
return Money;
}
public void ShowPros()
{
ck.ShowPros();
}
}
5)主函数
class Program
{
static void Main(string[] args)
{
//创建超市对象
SuperMarket sm = new SuperMarket();
//展示货物
sm.ShowPros();
//跟用户交互
sm.AskBuying();
Console.ReadKey();
}
}