C#forUnity快速入门(连载10)_C#里氏替换原则
更新:HHH   时间:2023-1-7


 C#forUnity编程语言快速入门教程(连载10)_C#OOP编程之里氏替换原则



    最近在Unity教学过程中,有学员对于C#的“里氏替换原则”(LSP)产生疑问表示不太理解。问题集中在不知道这个原则是做什么,有什么优势,具体在游戏开发(程序开发)过程中有什么借鉴与指导作用。


    就以上问题我总结梳理一下,与大家共同探讨。

    起源: 在我们的程序开发过程中,起初应该都是以完成项目功能为重点。但是随着软件规模的不断扩大,人们发现只完成功能是远远不够的。于是聪明的软件工程师,从大量的编程实践中开始总结一些共性的软件开发理论,以供后人规范编程,更有效率、更稳定的开发各种项目(包括游戏项目)。


   目前公认的一些编程原则:

   1: 单一职责原则 英文名称是Single Responsibility Principle,简称SRP
   2: 开闭原则 英文全称是Open Close Principle,简称OCP

   3: 里氏替换原则 英文全称是Liskov Substitution Principle,简称LSP
   4: 依赖倒置原则  英文全称是Dependence Inversion Principle,简称DIP

  

    在以上编程开发原则中,“开闭原则”与“里氏替换原则”比较类似,都是研究如何在相对稳定的编码环境中,尽可能的允许程序功能的变化。也就是最低的编码改动量,实现最大程度的软件项目变化性,来适应用户对软件系统不断变化的功能要求。

 

    解释如上概念:

    “开放-封闭原则”(即: 开闭原则)(OCP):   对于程序的扩展是开放的(Open for extension),对于程序的修改是封闭的(Closed for modification)

    “里氏替换原则”:子类型必须能够替换掉它们的父类型!(反之则不成立)


   “ 里氏替换”(也叫“里氏代换”原则)原则英文全称是Liskov Substitution Principle,简称LSP。 由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。 里氏替换原则就是要求写程序前,先建立抽象,通过抽象建立规范,具体的实现在运行时替换掉抽象,保证系统的高扩展性、灵活性。


     “开闭原则”要求我们写的程序修改尽量要少(即:封闭),但是对于功能的扩展确实开放的、允许的。 这其实是提了一个很高的编程目标,很好的一个指导原则,但是如何具体到实现上,是没有方法的,是不接“底气”的。 所以“里氏替换”就给了一个具体的实现方法的原则,就是让子类来无缝的对接父类,“子类型必须能够替掉父类”。


    “里氏替换原则”具体的要求可以表述如下:

      1: 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏替换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
      
      2: 我们在运用里氏替换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏替换原则其实就是“开闭原则”的具体实现手段之一!


     我们从序号为2的表述中可以看出,其实“里氏替换”原则就是一种更加具体的手段来实现“开闭原则”。 序号为2的理论其实就是我们常说的“动态多态性”的规则。


     针对以上理论,为了更好的理解,笔者给出“里氏替换原则”的实现参考代码,供C#初学者进行参考研究,有具体问题,可以留言,谢谢!


   //参考源码1: 以下是使用“虚方法”,来实现“里氏替换”。

    class Person
    {
        //虚方法
        public virtual void SpeakLanguage()
        {
            Console.WriteLine("Person, 说话方法");
        }
    }
    
    class MrLiu:Person
    {
        //方法重写
        public override void SpeakLanguage()
        {
            Console.WriteLine("MrLiu, 刘老师说中国话!");
        }
    }
    
    class Tom:Person
    {
        //方法重写
        public override void SpeakLanguage()
        {
            Console.WriteLine("Tom, Tom Speak Engligh!");
        }
    }
    
    class Test
    {
        public static void Main()
        {
              /*  “里氏替换原则”(即:动态多态性)测试 */

              //功能的定义部分
              //Person per = new MrLiu();   //打印“MrLiu, 刘老师说中国话!”
              Person per = new Tom();        //打印“Tom, Tom Speak Engligh!”

              //功能的实现部分

              per.SpeakLanguage();
        }
    }



   //参考源码2: 以下是使用“接口”,来实现“里氏替换”。

    interface ISpeakable
    {
        //抽象方法
        void SpeakLanguage();
    }
    
    class MrLiu:ISpeakable
    {
        //方法实现
        void ISpeakable.SpeakLanguage()
        {
            Console.WriteLine("MrLiu, 刘老师说中国话!");
        }
    }
    
    class Tom:ISpeakable
    {
        //方法实现
        void ISpeakable.SpeakLanguage()
        {
            Console.WriteLine("Tom, Tom Speak Engligh!");
        }
    }
    
    class Test
    {
        public static void Main()
        {
            /*  “里氏替换原则”(即:动态多态性)测试 */
            //功能的定义部分
            //ISpeakable Ispeak = new Tom();//打印“Tom, Tom Speak Engligh!”
            ISpeakable Ispeak = new MrLiu();//打印“MrLiu, 刘老师说中国话!”

            //功能的实现部分           
            Ispeak.SpeakLanguage();
        }
    }

   以上两段代码,分别用了父类“虚方法”与“接口”的方式分别实现“里氏替换”,当然还可以用“抽象类”来实现,究竟三者哪个方式,更应该优先使用呢? 现给出一个原则:


    “里氏替换”原则可以用以下方式来实现:
    1> 虚方法的动态多态性。
    2> 抽象方法的动态多态性。
    3> 接口方法的动态多态性。


      规则: 实现“里氏替换”原则(动态多态性): 能用接口,不用抽象方法,能用抽象方法,不用虚方法。


    好了,针对以上问题,笔者就总结以上内容,感兴趣的C#编程爱好者可以进行留言讨论。

返回开发技术教程...