深入浅出话多态(下)——牛刀小试

2024-02-05 03:32

本文主要是介绍深入浅出话多态(下)——牛刀小试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.多态的现实意义
       如果一个编程元素没有可以应用在软件工程中的现实意义,那将是一件不可容忍的事情。同理,如果你不了解一个编程元素的现实意义、不知道在编程时应该怎么用,就不能说自己懂得了这个编程元素。
       我的编程经验实在不多,就我个人感觉,多态最大的现实意义在于“代码的简化”。
       多态为什么能简化代码捏?
       先让我们用一句话概括多态的实现:首先要一个人父类,在这个父类的成员中,有一个 virtual 的(可以被子类重写的)方法。然后,有 N 多子类继承了这个父类,并且用 override 重写了父类的那个 virtual 方法——此时已经形成了一个扇形的多态继承图。当然,如果用作“父类”的是一个接口,那么在子类中就不是“重写”方法,而是“实现”方法了。
       一旦这个“继承扇”形成了,我们应该意识到——无论是父类还是子类,他们都有一个同名的方法,而且此同名方法在各个子类中是“个性化”的——它重写了父类的方法、并且子类与子类之间的这个同名方法也各不相同。
       在 程序的编写期,程序员总要预期用户可能进行的各种操作——比如对“继承扇”中每个子类的操作。程序编译完成、成为可执行文件并交付用户后,程序员就不能再 控制程序了,这时候程序只能听从用户的摆布。假设没有多态,那么为了让用户在调用每个子类的时候程序都能有正确的响应,程序员不得不为每个子类在内存中创 建一个实例——这样一来,程序复杂度增加的同时,性能也下降了。还好,这只是个假设……
       OK ,让我们还是来拿代码说事儿吧。下面给出两段代码,对比显示了多态的巨大优越性。
 
代码 1 非多态排比代码
 
//                          水之真谛                                      //
//            http://blog.csdn.net/FantasiaX               //
//                  
上善若水,润物无声                              //
using System;
using System.Collections.Generic;
using System.Text;

namespace Sample
{
      class OptimusPrime                    //
博派老大擎天柱
      {
           public void Transform()
           {
                 Console.WriteLine("Transform to a TRUCK...");
           }
      }

      class Megatron                          //
狂派老大威震天
      {
           public void Transform()
           {
                 Console.WriteLine("Transform to a GUN...");
           }
      }

      class Bumblebee                        //
大黄蜂
      {
           public void Transform()
           {
                 Console.WriteLine("Transform to a CAR...");
           }
      }

      class Starscream                       //
红蜘蛛
      {
           public void Transform()
           {
                 Console.WriteLine("Transform to a FIGHTER...");
           }
      }

      class Program                           //
主程序类
      {
           static void Main(string[] args)
           {
                 string number = string.Empty;
                
                 //
为每个类准备一个实例
                 OptimusPrime transformer1 = new OptimusPrime();
                 Megatron transformer2 = new Megatron();
                 Bumblebee transformer3 = new Bumblebee();
                 Starscream transformer4 = new Starscream();

                 while (true)                   //
无限循环
                 {
                      Console.WriteLine("Please input 1/2/3/4 to choose a transformer...");
                      number = Console.ReadLine();

                      switch (number)      //
根据用户选择,作出响应
                      {
                            case "1":
                                  transformer1.Transform();
                                  break;

                            case "2":
                                  transformer2.Transform();
                                  break;

                            case "3":
                                  transformer3.Transform();
                                  break;

                            case "4":
                                  transformer4.Transform();
                                  break;

                            default:
                                  Console.WriteLine("Do you want a TRACTOR ??");
                                  break;
                      }
                 }
           }
      }
}
代码分析:
1.        一上来是 4 个独立的类(相信这 4 位人物大家都不陌生吧……),这 4 个类有一个同名方法: Transform() 。虽然同名,但各自的实现却是“个性化”的、完全不同的——我们这里只用输出不同的字符串来表示,但你想啊——同样是有胳膊有腿的一个大家伙,变成汽车的方法跟变成飞机、变成枪怎么可能一样呢?
2.        进入主程序后,先是为每个类实例化一个对象出来,以备用户自由调用。这么做是很占内存的,如果为了优化程序,对每个类的实例化是可以挪到 switch 的每个 case 分支里的。
3.        一个无限循环,可以反复输入数字……
4.        switch…case… 根据用户的需求来调用合适的 Transformer Transform 方法。
 
 
代码 2 使用多态,简化代码
 
//                          水之真谛                                      //
//            http://blog.csdn.net/FantasiaX               //
//                  
上善若水,润物无声                              //
using System;
using System.Collections.Generic;
using System.Text;

namespace Sample
{
      class Transformer                                      //
基类
      {
           public virtual void Transform()
           {
                 Console.WriteLine("Transform to a ??? ???...");
           }
      }

      class OptimusPrime : Transformer            //
博派老大擎天柱
      {
           public override void Transform()
           {
                 Console.WriteLine("Transform to a TRUCK...");
           }
      }

      class Megatron : Transformer                       //
狂派老大威震天
      {
           public override void Transform()
           {
                 Console.WriteLine("Transform to a GUN...");
           }
      }

      class Bumblebee : Transformer                     //
大黄蜂
      {
           public override void Transform()
           {
                 Console.WriteLine("Transform to a CAR...");
           }
      }

      class Starscream : Transformer                    //
红蜘蛛
      {
           public override void Transform()
           {
                 Console.WriteLine("Transform to a FIGHTER...");
           }
      }

      class Program                                            //
主程序类
      {
           static void Main(string[] args)
           {
                 string number = string.Empty;

                 //
只准备一个变量即可,并且不用实例化
                 Transformer transformer;

                 while (true)                                   //
无限循环
                 {
                      Console.WriteLine("Please input 1/2/3/4 to choose a transformer...");
                      number = Console.ReadLine();

                      switch (number)                       //
根据用户选择,作出响应,运行期 " 动态 " 实例化
                      {
                            case "1":
                                  transformer = new OptimusPrime();
                                  break;

                            case "2":
                                  transformer = new Megatron();
                                  break;

                            case "3":
                                  transformer = new Bumblebee();
                                  break;

                            case "4":
                                  transformer = new Starscream();
                                  break;

                            default:
                                  transformer = null;
                                  break;
                      }
                     
                      if (transformer != null)               //
这里是本程序的核心
                      {
                            transformer.Transform();
                      }
                      else
                      {
                            Console.WriteLine("Do you want a TRACTOR ??");
                      }
                 }
           }
      }
}
代码分析:
1.        为了展示多态效果,先装备了一个基类。这个基类是一个常规的类——可以实例化、调用其方法。不过,使用抽象类或者接口来展示多态效果也完全没有问题,因此,你把 Transformer 类替换成下面两种形式也是可以的:
A )以抽象类做基类

      abstract class Transformer    // 基类,这是一个抽象类,方法只有声明没有实现
      {
           abstract public void Transform();
      }

B )以接口做基类
      interface Transformer           // 基接口,方法只有声明没有实现
      {
            void Transform();
      }
注意:如果使用的是基接口而不是基类,那么实现基接口的时候,方法不再需要 override 关键字。 原因很简单,接口中的方法是没有“实现”的,所以只需要“新写”就可以了、不用“重写”。如下:
      class OptimusPrime : Transformer      //
博派老大擎天柱
      {
           public void Transform()
           {
                 Console.WriteLine("Transform to a TRUCK...");
           }
      }
花絮:
记得有一次公开课上,一个兄弟问我:到底是用常规类合适,还是用抽象类或者用基接口合适呢?如何判断、如何在工程中应用呢?我感觉这个问题问的非常好——这个已经不是 C# 语言所研究的范畴了,而是软件工程的范畴,确切地说,这是“设计模式”( Design Pattern ) 的范畴。当你已经对类的封装、继承、多态了如指掌后,不满足于这种小儿科的例子程序而打算用这些知识写出漂亮的软件工程时,那么你会发现:如何设计类、什 么应该被设计成类、什么不应该,与耦合,类与类之间如何继承最稳定、最高效,类与类之间如何平衡内聚……这些问题都非常重要却又让你感觉无从下手。那么 OK ,去看《设计模式》吧,去学习 UML 吧!恭喜你,你上了一个大台阶!
2.        在本例中,只声明了一个多态变量,而且没有实例化。实例化的步骤挪到 case 分支里去了。我记得有的书里管这样的形式叫“动态实例化”。
3.        switch…case… 分支中,根据用户的选择实例化 transform 引用变量。
4.        最后的 if…else… 是程序的核心。在 if true 分支里,因为各类的 Transform() 方法是同名而且是 virtual/override 继承的,所以能够体现出多态性。
二.牛刀小试
       光写上面那种没什么实际用处的例子程序还真没多大意思,也就唬弄唬弄新手、讲讲原理还行。下面这个例子是多态在实际应用中的一个例子。这个例子在《深入浅出话事件》里提到过,是有关于 WinForm 程序的事件中那个 object 类型的 sender 的。
       OK ,让我们来考虑这样一种情况:我在窗体上放置了 50 Control 和一个 ToolTip 。现在要求当鼠标指向某一个 Control 的时候, ToolTip 要显示当前所指 Control 的全名( Full Name )。
呵呵,这个听起来并不难,对吧!你可能会这样想:
1.        获得当前 Control 的名字,可以用这个 Control ToString() 方法。
2.        在每个 Control MouseEnter 事件里,让 ToolTip 显示上一步所获得的字符串就 OK 了。
3.        比如 button1 MouseEnter 事件响应函数写出来就应该是这样的:

           private void button1_MouseEnter(object sender, EventArgs e)
           {
                 string fullName = button1.ToString();
                 toolTip1.SetToolTip(button1, fullName);
           }

comboBox1 MouseEnter 事件响应函数则是:

           private void comboBox1_MouseEnter(object sender, EventArgs e)
           {
                 string fullName = comboBox1.ToString();
                 toolTip1.SetToolTip(comboBox1, fullName);
           }


唔……窗体里有 50 Control ,你怎么办呢?
噢!你说可以用“复制 / 粘贴”然后再改动两处代码?
真是个好主意!!不过,你打算在什么地方出错呢?这种“看起来一样”的复制 + 粘贴,是 Bug 的一大来源。况且我这里只有 50 Control ,如果是 500 个,你打算怎么办呢?
佛说:前世的 500 次回眸,换得今生的 1 次擦肩而过;可他没说今生的 500 Ctrl+C/Ctrl+V 能在下辈子奖你个鼠标啊 :P
OK ,我知道你有决心和毅力去仔细完成 50 次正确的 Ctrl+C/Ctrl+V 并且把两处代码都正确改完。当你完成这一切之后,市场部的兄弟告诉我们——客户的需求升级了!客户要求在鼠标移向某个 Control 时不但要显示它的 Full Name ,而且 ToolTip 的颜色是随机的!!
>_< …… @#^&(#$%^@#
50 处的修改,不要漏掉喔……程序员吐吧吐吧,不是罪!
 
       ……拜 OO 之神所赐,我们有多态……噩梦结束了。让我们看看多态是如何简化代码、增强可扩展性的。
       首先打开 MSDN ,我要 Show 你一点东西。请你分别查找 Button 类、 TextBox 类、 ListBox 类、 ComboBox 类……它们的 ToString() 方法。是不是都可以看到这样一句注释:
l          ToString               Overridden.  (这是 Button 类的,是重写了父类的。)
l          ToString         Returns a string that represents the TextBoxBase control. (Inherited from TextBoxBase.) (这是 TextBox 的,说是从 TextBoxBase 继承来的,我们追查一下。)
l          ToString         Overridden. Returns a string that represents the TextBoxBase control. (这是 TextBoxBase 的,也是重写了父类的。 TextBox 继承了它,所以仍然是重写的。)
l          ToString         Overridden. Returns a string representation of the ListBox. (这是 ListBox 的,也明确指出是重写的。)
……
这些 Control 都是重写的谁的 ToString() 方法呢?其中的细节我就不说了——这这个重写链的最顶端,是“万类之源”—— Object 类。也就是说,在 Object 类中,就已经包含了这个 ToString() 方法。 Object 类在 C# 中正好对应 object 这个 Keyword
一切问题都解决了!让我们用多态来重构前面的代码!
1.        手动书写(或者改造某个 Control MouseEnter 响应函数),成为如下代码:
           private void common_MouseEnter(object sender, EventArgs e)
           {
                 //
用户的第一需求:显示 ToolTip
                 string fullName = sender.ToString();
                 Control currentControl = (Control)sender;
                 toolTip1.SetToolTip(currentControl, fullName);

                 //
用户的第二需求:随机颜色
                 Color[] backColors = new Color[] { Color.CornflowerBlue, Color.Pink, Color.Orange };
                 Random r = new Random();
                 int i = r.Next(0, 3);
                 toolTip1.BackColor = backColors[i];

                 //
用户的第 N 需求: ……

           }

2.        将这个事件处理函数“挂接”在窗体的每个 Control MouseEvent 事件上,方法是:在“属性”面板里切换到 Control 的“事件”页面(点那个小闪电),然后选中 MouseEvent 事件,再点击右边的向下箭头,在下拉菜单中会出现上面我们手写的函数——选中它。如图:

3.        为每一个 Control MouseEvent 事件挂接这个响应函数。一个简短的、可扩展的程序就完成了!
代码分析:
1.        函数之所以声明成:
private void common_MouseEnter(object sender, EventArgs e){…}
是为了与各 Control MouseEnter 事件的委托相匹配。如果你不明白为什么这样做,请仔细阅读《深入浅出话事件》的上下两篇。
2.        核心代码:
string fullName = sender.ToString();
体现了多态。看似是 sender ToString() 方法,但由于各个类在激发事件的时候,实际上是以 this 的身份来发送消息的, this 在内存中指代的就是一个具体的 Control ——如果是 button1 发送的消息,那么这个 this 在内存中就是指向的 button1 ,只不过指向这块内存的引用变量是一个 object 类型的变量——典型的多态。又因为 Button 类继承自 Object 类,并且重写了 Object ToString() 函数,所以在这里调用 sender.ToString() 实际上就调用了 button1.ToString()
3.        显式类型转换,为 toolTip1 SetToolTip() 函数准备一个参数:
Control currentControl = (Control)sender;
其实,这也是多态的体现:子类可以当作任意其父类使用。 sender 虽然是一个 object 类型的变量,但它实际上是指向内存中的一个具体的 Control 实例—— MouseEnter 事件的拥有者(比如一个 Button 的实例或者一个 TextBox 的实例)。而 Button TextBox 等类的父类就是 Control 类,所以完全可以这么用。
4.        后面的代码就非常简单了,不说了。
       至此,一个结构清晰,代码简单(只有原来的1/50长度,操作也为原来的1/20不到),便于维护而且扩展性极佳的程序就新鲜出炉了!没有多态,这是不可能实现的。
 
作业:
       自己动手把这个 WinForm 程序完成,并且确保自己能够分析清楚每句代码的含意。
 
花絮:
       现实当中, WinForm 程序的一大部分代码都是由 Visual Studio 2005 为我们写好的,鳞次栉比、非常好看——但初学者常被搞的晕头转向。没别的办法:大胆打开那些代码、仔细察看、动手跟踪跟踪、修改修改——别怕出错!经验大都是从错误中萃取出来的精华,有时候几十个错误才能为你换来那么一丁点领悟。高手不仅仅是比我们看书多,更重要是犯的错比我们多,呵呵……
 
唉……长舒一口气。
最后我想说的是:要想读懂这些文章,首先要慢慢读——我写它的时候思路是清清楚楚的,但我的思想是我的思想,理解它的时候要一句一句看,说真的,错过一两个字都有可能读不懂。还有就是代码,一定要自己敲一遍。
       如果大家有什么疑问,别客气,在后面跟帖发问就是了。只要有时间,我会一一作答。如果我有哪里写的不对,也请各位高手多多指教,我会立刻更正。
       到此为止,这篇又臭又长的文章可以 OVER 了——砖我是抛出去了,等您的玉呢!
 

这篇关于深入浅出话多态(下)——牛刀小试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/679634

相关文章

JavaSE——封装、继承和多态

1. 封装 1.1 概念      面向对象程序三大特性:封装、继承、多态 。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节 。     比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器, USB 插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作的却是CPU 、显卡、内存等一些硬件元件。

【JVM】JVM栈帧中的动态链接 与 Java的面向对象特性--多态

栈帧 每一次方法调用都会有一个对应的栈帧被压入栈(虚拟机栈)中,每一个方法调用结束后,都会有一个栈帧被弹出。 每个栈帧中包括:局部变量表、操作数栈、动态链接、方法返回地址。 JavaGuide:Java内存区域详解(重点) 动态链接 动态链接:指向运行时常量池中该栈帧所属方法的引用。 多态 多态允许不同类的对象对同一消息做出响应,但表现出不同的行为(即方法的多样性)。 多态

java基础总结14-面向对象10(多态)

面向对象最核心的机制——动态绑定,也叫多态 1 通过下面的例子理解动态绑定,即多态 package javastudy.summary;class Animal {/*** 声明一个私有的成员变量name。*/private String name;/*** 在Animal类自定义的构造方法* @param name*/Animal(String name) {this.name = n

OOP三个基本特征:封装、继承、多态

OOP三个基本特征:封装、继承、多态 C++编程之—面向对象的三个基本特征 默认分类 2008-06-28 21:17:04 阅读12 评论1字号:大中小     面向对象的三个基本特征是:封装、继承、多态。     封装 封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。   封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信

深入浅出SRS—RTMP实现

RTMP 直播是 SRS 最典型的使用场景,客户端使用 RTMP 协议向 SRS 推流,使用 RTMP 协议从 SRS 拉流,SRS 作为一个 RTMP 直播服务器实现媒体的转发。同时,RTMP 是 SRS 的中转协议,其他协议之间的互通需要先转为 RTMP,因此,理解 SRS RTMP 直播实现是理解其他协议实现的重要前提。本文主要分析 SRS RTMP 直播功能的实现原理,相关概念和配置请参考

泛型第三课,自定义泛型、无多态、通配符、无泛型数组

泛型没有多态 package com.pkushutong.genericity4;/*** 多态的两种形式* 注:泛型没有多态* @author dell**/public class Test01 {public static void main(String[] args) {Fruit f = new Fruit();test(new Apple());}//形参使用多态publi

Python中的方法重写与多态:解锁编程的无限可能

在编程的世界里,灵活性与扩展性往往是衡量一个语言是否强大、易于维护的关键指标。Python,作为一种被广泛使用的高级编程语言,不仅以其简洁易读的语法赢得了众多开发者的喜爱,更因其支持多种面向对象特性而备受青睐。其中,“方法重写”与“多态”便是两个核心概念,它们不仅能够极大地提高代码的复用性和可维护性,还能帮助我们构建更加灵活、健壮的软件系统。本文将通过一系列由浅入深的例子,带你一起探索这两个概念的

深入浅出Java垃圾回收机制

对于Java开发人员来说,了解垃圾回收机制(GC)有哪些好处呢?首先可以满足作为一名软件工程师的求知欲,其次,深入了解GC如何工作可以帮你写出更好的Java应用。   这仅仅代表我个人的意见,但我坚信一个精通GC的人往往是一个好的Java开发者。如果你对GC的处理过程感兴趣,说明你已经具备较大规模应用的开发经验。如果你曾经想过如何正确的选择GC算法,那意味着你已经完全理解你所开发的应用的特点

《C++中的面向对象编程三大特性:封装、继承与多态》

在 C++编程的广阔世界中,面向对象编程(Object-Oriented Programming,OOP)的三大特性——封装、继承和多态,犹如三把强大的利器,帮助程序员构建出高效、可维护和可扩展的软件系统。本文将深入探讨如何在 C++中实现这三大特性,并通过具体的代码示例展示它们的强大之处。 一、封装(Encapsulation) 封装是将数据和操作数据的方法封装在一个类中,以实现信息隐藏和数

多态的概念及实现

目录 前言 2. 多态的定义及实现 2.1多态的构成条件 2.2 虚函数 2.3虚函数的重写 重写、重载、重定义的辨析 虚函数重写的两个例外: 2.4 C++11 override 和 final 1. final:修饰虚函数,表示该虚函数不能再被重写 这样就会报错。 2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。 2.5