CH09-使用抽象方法实现多态

本章目标

  1. 理解里氏替换原则
  2. 会使用父类类型作为参数
  3. 理解抽象类和抽象方法
  4. 理解虚方法和抽象方法的区别

本章内容

1、里氏替换原则

  1. 概念:

    里氏替换原则(LSP)在一个软件系统中,子类对象可以替换所有使用的父类对象,且程序行为没有变化

  2. 里氏替换原则详细解释

    1. 子类必须完全实现父类的方法:子类不应该改变父类已有方法的预期行为。如果父类中的某个方法在子类中没有被正确地实现(或者说,子类改变了父类方法的预期行为),那么当使用这个子类替换父类时,就可能会导致程序出现错误。
    2. 子类可以增加自己的特有方法:子类可以扩展父类的功能,但这不应该影响父类方法的行为。
    3. 子类返回的类型必须与父类方法返回的类型兼容:如果父类方法声明返回一个类型,那么子类中被覆盖的方法也应该返回相同类型或者其子类型。
    4. 子类不应该抛出比父类方法更多的异常:子类方法抛出的异常应该与父类方法抛出的异常类型相同或者是其子类。
    5. 子类应该尊重父类的约定和前置条件:父类在设计中可能有一些前置条件或者约束,子类在实现时必须遵循这些前置条件和约束。
  3. 示例:

    1
    2
    3
    4
    5
    6
    7
    SE ai = new SE();
    SE joe = new SE();
    PM gates = new PM();
    List<Employee> empls = new List<Employee>();
    empls.Add(ai);
    empls.Add(joe);
    empls.Add(gates);

    说明:

    1. 集合中保存的对象是父类对象,但是在添加对象时可以直接添加子类对象,则证明子类可以替换父类,这就是满足里氏替换原则。
  4. 提问:一个鸟类,一个鸵鸟类,如果鸟可以飞,鸵鸟类可以继承鸟类吗?利用里氏替换原则思想,可以继承吗?

    1721790701771

2、is与as操作符

  1. is 检查对象是否与指定类型兼容

    if (empls[i] is SE) {}: 判断empls集合的元素是否是SE对象

  2. as 用于在兼容的类型之间执行转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (int i = 0; i < empls.Count;i++ )
    {
    if (empls[i] is SE)
    {
    //类型转换:一种类型转换方式:将empls集合的元素转换为SE对象;转换失败返回null
    SE se = empls[i] as SE;
    Console.WriteLine(se.SayHi());
    }
    }

    经验:

    用as操作符进行类型转换不会产生异常,但这并不代表不需要异常处理,如果类型转换失败返回null

3、多态应用:将父类作为方法参数实现多态

  1. 案例:某公司员工回家,可以选择不同交通工具(小汽车、地铁、自行车),每种交通工具具有行驶的行为,编程模拟员工回家的过程。

    1721791942189

    具体分析:

    1. 交通工具具有继承关系

      1721792057740

    2. 员工回家的方法:GoHome(TrafficTool tool),将父类作为参数类型

    演示代码:

    1. 实现交通工具的继承关系:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      //交通工具基类
      public class TrafficTool
      {
      public virtual void Run()
      {
      Console.WriteLine("车在行驶!");
      }
      }
      //自行车类
      class Bicycle:TrafficTool
      {
      public override void Run()
      {
      Console.WriteLine("自行车奔跑中!");
      }
      }
      //小汽车类
      class Car:TrafficTool
      {
      public override void Run()
      {
      Console.WriteLine("小汽车在行驶!");
      }
      }

      //地铁类
      class Tube:TrafficTool
      {
      public override void Run()
      {
      Console.WriteLine("地铁运行中!");
      }
      }
    2. 员工要回家,定义GoHome方法

      父类作为参数使用,可接收多种子类类型

      1
      2
      3
      4
      5
      6
      7
      public class Employee{
      public void GoHome(TrafficTool tool)
      {
      Console.WriteLine("员工:"+this.Name);
      tool.Run();
      }
      }
    3. 测试类,实现员工使用各种交通工具回家

      1
      2
      3
      4
      5
      6
      7
      List<Employee> empls = new List<Employee>();
      empls.Add(ai);
      empls.Add(joe);

      //员工选择不同交通工具回家
      empls[0].GoHome(new Bicycle());//传递实际创建的子类对象,tool.Run()执行相应子类的Run()
      empls[1].GoHome(new Tube());
    4. 小结:

      用虚方法实现多态的步骤

      1. 子类重写父类的虚方法
      2. 两种方式:
        1. 创建父类变量,实例化子类对象
        2. 把父类类型作为参数类型,该子类及子类对象作为参数传入
      3. 运行时,根据实际创建的对象决定执行哪个方法

4、抽象方法实现多态

  1. 为什么要使用抽象方法?

    有的父类的方法,不具有实际意义,比如:交通工具类是抽象的概念,根本不可能实例化,Run()方法不具有实际意义!

    1
    2
    3
    4
    5
    6
    7
    8
    public class TrafficTool
    {
    public virtual void Run()
    {
    Console.WriteLine("车在行使!");
    }
    }

    使用抽象方法之后:

    1
    2
    3
    4
    public abstract class TrafficTool
    {
    public abstract void Run();
    }
  2. 语法:

    1
    2
    3
    4
    [访问修饰符]  abstract  class 类名
    {
    [访问修饰符] abstract 返回类型 方法名();
    }
  3. 抽象类:

    1. 抽象类用来列举一个类所需要的行为

    2. 抽象类是一种特殊的类,它不能被实例化,只能作为基类来派生出其它的具体类。

      1
      2
      3
      4
      5
      6
      7
      public abstract class TrafficTool
      {
      public abstract void Run();
      }
      static void Main(){
      TrafficTool tool=new TrafficTool();//报错:抽象类不能实例化
      }
    3. 抽象类使用abstract关键字来声明,其中可以包含抽象方法、虚方法、常规方法和属性。

    4. 抽象类的主要作用是为其派生类提供一个通用的抽象基类。

    5. 抽象类不能是密封或者静态的。

      1
      2
      3
      public sealed  abstract class Person//报错:抽象类不能是密封或者静态的
      {
      }
  4. 抽象方法:

    1. 一个没有实现的方法,它只有定义并且声明,没有具体实现。

    2. 抽象方法使用abstract关键字来声明,在抽象类中定义,而其具体实现必须在派生类中实现。

    3. 抽象方法的主要作用是为其派生类提供一个统一的方法接口。

    4. 抽象类注意事项:

      1. 抽象方法必须使用abstract 修饰的方法

      2. 不允许有方法体

        1
        2
        3
        4
        public abstract class TrafficTool
        {
        public abstract void Run(); //没有方法体,且用abstract修饰
        }
      3. 子类必须重写override父类的抽象方法,除非子类也是抽象的

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public abstract class TrafficTool
        {
        public abstract void Run(); //没有方法体,且用abstract修饰
        }

        public class Car:TrafficTool{
        public override void Run(){//子类必须重写父类的抽象方法
        .....
        }
        }
      4. 抽象方法不能使用private修饰,因为派生类必须访问它

        1
        private abstract void SayHello();//报错:抽象方法不能为抽象的
      5. 一个类中出现了抽象方法,那么此类必须是抽象类,也就是说:抽象方法只能定义在抽象类中。

5、抽象类应用

  1. 示例:通过抽象类完成以下功能:

    1721807810025

    1. 代码演示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
       public abstract class Animal
      {
      public abstract void Jiao();
      }
      public class Cat : Animal
      {
      public override void Jiao()
      {
      Console.WriteLine("喵喵....");
      }
      }

      public class Dog : Animal
      {
      public override void Jiao()
      {
      Console.WriteLine("汪汪....");
      }
      }

      static void Main(string[] args)
      {
      Animal cat = new Cat();
      Animal dog = new Dog();

      List<Animal> list = new List<Animal>() { cat, dog };
      foreach (Animal animal in list)
      {
      animal.Jiao();
      }
      }
  2. 示例:使用抽象类实现员工使用交通工具回家功能:

    1. 修改父类代码:

      1
      2
      3
      4
      5
      //交通工具基类
      public abstract class TrafficTool
      {
      public abstract void Run();
      }
    2. 其他代码,基本上一致,则省略….

6、抽象方法和虚方法对比:

  1. 应用场景对比:

    1. 抽象类和抽象方法

      1. 抽象类通常代表一个抽象的概念

      2. 抽象方法约束子类对象的行为

        比如:抽象的动物类,具有抽象方法吃()、喝()

    2. 虚方法

      1. 具体类的默认实现,提供其子类扩展实现方式

        比如:数据库连接类,Connect()方法实现与SQL Server的连接,数据库连接子类继承并重写它,实现与其它数据库的连接。

  2. 详细对比:

    抽象方法 虚方法
    用 abstract 修饰 用 virtual 修饰
    不允许有方法体 要有方法体,哪怕是一个分号
    必须被子类 override 可以被子类 override
    只能在抽象类中 除了密封类都可以写

本章总结

1721808772545

本章作业

  1. 完成课堂演示案例:某公司员工回家,可以选择不同交通工具(小汽车、地铁、自行车),每种交通工具具有行驶的行为,编程模拟员工回家的过程。

  2. 实现器乐演奏:

    1. 需求说明

      公司举办器乐大赛,员工可以选择不同的乐器进行演奏,模拟演奏过程

    2. 提示:

      不同器乐继承自Instrument(乐器)类

      员工具有Play(乐器类型)方法

      1
      public void Play(Instrument XX) {XX.Play();}
    3. 效果图:

      1721809024929

  3. 实现员工执行工作列表、查看工作指标完成情况

    1. 实现效果图:

      1721809203677

    2. 详细分析:

      1. 工作类型有很多且执行指标不同
        1. 编码、测试、审核……
        2. 各工作同属于日常工作,只是表现形式不同,可使 用多态
    3. 类图:

      1721809321383

    4. 提示代码:

      1. 定义父类

        1
        2
        3
        4
        5
        6
        7
        public abstract class Job
        {
        ......定义公共属性…..
        //执行
        public abstract void Execute();
        }

      2. 定义子类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        public class CodeJob:Job
        {
        //实现Job的抽象Execute()方法
        public override void Execute()
        {
        FrmCodeExe frmCodeExe = new FrmCodeExe(this);
        frmCodeExe.ShowDialog();
        }
        }
  4. 员工执行工作列表

    1. 需求说明:

      1. 实现员工执行工作列表
        1. 编码工作指标项:有效编码行数、遗留问题、工作日
        2. 测试工作指标项:测试用例个数、发现的Bug数、工作日
    2. 指导:实现思路

      1. 搭建窗体
      2. 编写Job类
      3. 编写Job子类(TestJob、CodeJob),并实现抽象Execute()
      4. 方法实现右键“执行”响应事件
    3. 效果图:

      1721809612573

  5. 查看工作指标完成情况:

    1. 需求说明:

      实现员工查看工作指标完成情况

    2. 效果图:

      1721809689466

  6. 为宠物看病

    1. 需求说明

      1. 假设兽医每天要进行对不同生病的宠物治疗的工作,宠物包括狗、小鸟、荷兰猪等等
      2. 试编写一个程序模拟兽医为宠物看病的过程
        1. 不同宠物受伤接受治疗的方式不同。兽医为宠物看病时,不能使用判断语句判断宠物类型
        2. 使用抽象方法实现为不同宠物看病,不可以使用方法的重载
    2. 效果图:

      1721809816193