C#多态

来源:互联网 发布:长沙优化公司 编辑:程序博客网 时间:2024/04/30 00:58

  多态,面向对象编程的核心概念。
                请相信:无论怎样强调多态在OOP中的重要性,都不为过。
                不理解它,也就不会真正明白什么是OOP。

一、静态方法与类型适应

        A、方法的分类: 

            所谓联编是指编译器在什么时候确定调用那段程序代码,如果对某段程序代码的调用能够在编译时确定,则称为静态联编或先期联编;否则称为动态联编或滞后联编;
            按照类的编译方式,方法可分为实例方法;虚拟方法;抽象方法等,这些特性可以实现对象的多态性。

        B、实例方法

            如果在类的声明中,在方法后不使用virtual或abstract等方法指示符,则该方法就是实例方法。这表明,我们在类中声明的方法缺省为实例方法。
            在调用实例方法时,方法名前的对象类型唯一确定调用的是哪个方法。

       C、类型适应 【定义】
            类的继承关系是按照层次结构组织的。面向对象的理论规定,祖先类兼容它所有的后代类,而后代类则不兼容于它的祖先类型。                

           因此我们可以:
              FatherClass Fclass1, Fclass2;                 
              Fclass11 = new FatherClass();
              Fclass12 = new SunClass();
           这种类型之间的兼容性称为类型适应。
           但不可以:
             SunClass Sclass;   SunClass = new FatherClass();    //error
       D、类型适应 【应用】
           
            在程序中我们我们可以大量的见到如下的代码:
            private void button1_Click(object sender, EventArgs e);
            那么sender参数是什么?它是对object对象的引用。我们在程序中有这样的object对象实例嘛?没有!程序中使用的所有对象都是object对象的继承类类型,而不是object类型本身。如果不允许类型适应,则必须为所有的继承类型都重载一遍如下方法:
            private void button1_Click(button sender, EventArgs e);
private void checkBox1_CheckedChanged(checkBox sender, EventArgs e);
            这样书写很不方便,而且如果我们声明了一个新的类型该怎么办呢?该方法必须在C#中实现并且在编译前予以解释,在编译前如何知道我们声明了新类呢?所以,允许子类向父类任意赋值就是最自然的选择,参数实际传递的是对子对象对象实例的引用。

 

 

二、虚拟方法
        A、强制类型转换的缺陷

            如前所述:我们可以
                FatherClass  Fclass = new SunClass();
            但却只能访问父类类型定义的方法或数据域,而在很多时候我们需要访问子类的方法或数据域,该如何呢?
            通过强制类型转换总是可以的,但如此操作对类类型很不安全,很可能将它转换为一个错误的类型,我们总知道应该将其转换为什么类型,但问题是,编译器知道嘛?
            比如类库中有方法  Work(FatherClass  Fc);
            实际使用时候需要传入FatherClass的子类,比如SunClass, SunClass1等等,我们根据需要从FatherClass派生了新的子类NewSunClass,并重新实现了FatherClass类中方法比如TestFunc(),那么如何在Work(FatherClass  Fc);中调用到NewSunClass. TestFunc(); 呢?
            就我们讲过的知识,我们没有任何办法!!

        B、虚拟方法

            实际解决的方法就是使用virtual关键字将父类中的TestFunc()声明为虚拟的,这样在调用虚拟方法时,即使将变量转换为父类类型,只要它实际引用的是子类类型的对象实例,调用的都将是子类的方法。需要注意的是子类中TestFunc()方法需用override 修饰,这个叫覆盖。
            演示代码如下:
         public class FatherClass
        {
           public virtual void TestFunc()
          {
             MessageBox.Show("FatherClass TestFunc "); 
          }
       }


       public class SunClass : FatherClass
      {
         public override void TestFunc()
        {
           MessageBox.Show("SunClass TestFunc "); 
        }
      }
            使用如下:
            FatherClass Fclass = NewSunClass();
Fclass.TestFunc();
            在编译如上代码时候,编译器对于指向那个TestFunc()方法是不知道的,只有根据对象实例本身的类类型在程序运行过程中动态确定,这也就是滞后联编的意思所在。
            也就是说父类和子类具有相似性,我们常说“有其父必有其子”,在面向对象领域中,这句话是不对的,它应为“有其子必有其父”。
            父亲的行为象儿子,而不是儿子的行为象父亲,这是理解多态性的关键。

三、方法指示符
    A、方法的重定义

            先给出一段代码示例:  
          public class FatherClass
         {
            public void TestViMethod()
           {  
                MessageBox.Show("FatherClass TestMethod"); 
           }

           public void TestaBMethod()
          {  
                 MessageBox.Show("FatherClass TestMethod");  
          }
       }

 

 

     public abstract class SunClass : FatherClass
    {
        public virtual new void TestViMethod()
       {  
                 MessageBox.Show("SunClass TestMethod");    
       }
        public abstract new void TestaBMethod(){}
    }
            没有错,在C#中,如上代码完全可以运行!
在方法重定义的场合,在子类中同时存在着自己和父类的重定义函数,只是由于可见性的关系,父类的函数不可见而已。
            刚才我们演示的重定义的方法中,既有重定义为虚拟方法也有重定义为抽象方法的【其实抽象方法是隐式的虚拟方法】,没错,这个完全可以,如果要在子类的后代中对该方法进行覆盖,则必须将它定义为虚拟方法或动态方法。
            重定义和覆盖构成了类层次中定义同名静态方法的两种手段。但是在编码中,重定义不建议使用,知道就可以了。

        B、方法的覆盖

            这里需要对覆盖指出几点:
            其一:所谓覆盖指的是对父类中同名函数的覆盖,所以子类中的方法申明必须与父类完全相同。
            其二:不能覆盖父类中不存在的方法。
            其三:只能覆盖父类中的虚拟方法【抽象方法是隐式虚拟方法】
            其四:如果在子类中覆盖了父类的方法,则应在子类中重新实现它。

四、动态联编机制       
        A、概念
           
            将特定的属性与程序代码的特定单元关联起来的过程称为联编;如果联编出现在程序运行前,则为静态联编;如果联编出现在程序运行时,则为动态联编。
            由静态联编所确定的关联一旦建立,就不会再改变;二有动态联编所确定的关联可以改变或被其他关联所取代。

        B、C#中的动态联编
           
            C#中的动态联编使用虚拟方法或者抽象方法来实现。我们将类中的一些方法声明为虚拟或者抽象的,就意味着这些方法在子类中会有不同的实现,编译器为虚拟方法和抽象方法调用生成调用代码,这些代码在运行时能按照对象实例本身的类类型动态选择所定义的虚拟或者抽象方法。
            动态联编与继承机制使我们可以在一个高水平的抽象层次上使用继承层次结构。我们仅需在该层次结构的顶层从事有效的工作,而不必关心底层子类的存在。这对于程序设计与实现都是非常有益的。

五、进入主题:多态性
        A、多态的定义

            封装可以隐藏实现细节,使得代码模块化,继承可以扩展已存在的代码模块,它们的目的都是为了代码重用,而多态则是为了实现另外一个目的:接口重用。
            Bruce Eckel说:“不要犯傻,如果它不是晚绑定,它就不是多态!” 【 <Thinking in Java> 作者 】
            那么该如何理解多态呢?
            虚拟方法是类中声明的非静态方法,在类中该方法声明带有virtual或者abstract,我们称带有虚拟方法的类成为多态类。
            在后面的讨论中,我们将会看到,多态类提供了相同的操作接口,但能服务于不同的应用要求。当基于父类抽象设计程序,而所设计的程序又需要适应子类操作变化时,就需要使用虚拟方法通知编译器这种可能的变化,使编译器为虚拟方法调用生成特别代码,以便运行时对虚拟方法调用采用动态联编。
            基于此,我们说虚拟方法与覆盖的实现就是多态。
            所以我们说,实现了如下多态三大要素,也就实现了多态:
                第一:在父类中定义虚拟方法。
                第二:在子类中覆盖父类中的虚拟方法。
                第三:申明父类实例的变量,则该变量既可以指向父类的实例,也可以指向子类的实例。当变量指向父类实例时,调用的是父类方法;当变量指向子类实例时,调用的是子类同名方法。

        B、多态的实例

            SayHello_1
                Form1启动 --没有使用多态
                Form2启动 --使用多态  [ 有什么好处呢?]
                Form3启动 --重构多态  [ 继续重构 ]
                Form4启动 -- 简单工厂模式

        C、警惕多态
           
            当在子类覆盖父类的虚拟方法时候,有如下注意事项:
            第一:在很多程序语言中,子类override父类的方法以后,子类的可见性可以不低于原先声明,但是C#可见性必须和父类相同
            第二:子类override父类的虚拟方法,方法申明必须完全一致
            第三:动态联编的类的this不确定,它或者指向父类或者指向子类,其具体在动态联编时确定,使用时候需要注意

六、抽象方法与抽象类
            A、抽象方法注意事项
               
                包含有抽象方法声明的类叫做抽象类
                第一:在调用抽象方法时候,不能引用父类类型对象实例的该方法。【也即抽象类不能实例化】
                第二:如果子类没有覆盖父类的抽象方法,则子类继承来的该方法仍然为抽象方法,同样的抽象子类也不能实例化

            B、抽象类
               
                刚才提到了包含有抽象方法声明的类叫做抽象类。
                抽象类不能实例化;
                抽象类可以包含有非抽象方法
                设计好的抽象类定义了子类所应该遵循的一组协议,
                因此,抽象类反映了子类的共同行为,从而可以传达设计意图。


七、题外话:多态的重要性


            在OO的程序设计和开发中,多态是道坎,只有在开发或者设计中,能够灵活的使用多态,你才会体会到OO中的乐趣。
            设计模式是OO的另外一道坎,很多人在学习设计模式时候,感到很吃力,其实也是没有吃透多态的缘故。
            设计模式中处处潜藏着多态的身影,可以毫不夸张的说,存在类泛化【类继承】的模式中,一定有多态的存在,而大多模式都存在类继承,也自然存在着多态在设计模式中的使用,足见多态的重要。
            上升到共同类库,应用框架的开发,又是以设计模式为基础的,多态的重要性可见一斑。

八、结束

                            谢谢!

 

原创粉丝点击