0. 浪子 《今天你多态了吗?》 提出两个这样的 问题

1) “使用基类继承多态,有一点特别需要注意的就是:基类(抽象或者非抽象)中需要获得多态效果的成员必须有 abstract 或 virtual 修饰。”使用 new 来重写的成员不能形成多态吗? 2) “多态就是使得你能够用一种统一的方式来处理一组各具个性却同属一族的不同个体的机制。” new 重写后的成员是否符合了这个范畴?

1. 你通常怎样用多态?

假设我有一个类,里面有一个 PrintStatus 方法,用于打印实例的当前状态,我希望该类的派生类都带有一个 PrintStatus 方法,并且这些方法都用于打印其实例的当前状态。那么我会这样表达我的愿望:

//  Code #01

class  Base
{
     public   virtual   void  PrintStatus()
     {
        Console.WriteLine( " public virtual void PrintStatus() in Base " );
    }
}

于是我可以写一个这样的方法:

//  Code #02

public   void  DisplayStatusOf(Base[] bs)
{
     foreach  (Base b  in  bs)
     {
        b.PrintStatus();
    }
}

bs 中可能包含着不同的 Base 的派生类,但我们却可以忽略这些“个性”而使用一种统一的方式来处理某事。在 .NET 2.0 中,XmlReader 的 Create 有这样一个版本:

public   static  XmlReader Create(Stream input);

你 可以向 Create 传递任何可用的“流”,例如来自文件的“流”(FileStream)、来自内存的“流”(MemoryStream)或来自网络的“流 ”(NetworkStream)等。虽然每一中“流”的工作细节都不同,但我们却使用一种统一的方式来处理这些“流”。

2. 假如有人不遵守承诺...

DisplayStatusOf 隐含着这样一个假设:bs 中如果存在派生类的实例,那么该派生类应该重写 PrintStatus,当然必须加上 override 关键字:

//  Code #03

class  Derived1 : Base
{
     public   override   void  PrintStatus()
     {
        Console.WriteLine( " public override void PrintStatus() in Derived1 " );
    }
}

你可以把这看作一种承诺、约定,直到有人沉不住气...

//  Code #04

class  Derived2 : Base
{
     public   new   void  PrintStatus()
     {
        Console.WriteLine( " public new void PrintStatus() in Derived2 " );
    }
}

假设我们有这样一个数组:

//  Code #05

Base[] bs  =   new  Base[]
{
     new  Base(),
     new  Derived1(),
     new  Derived2()
} ;

把它传递给 DisplayStatusOf,则输出是:

//  Output #01

//  public virtual void PrintStatus() in Base
//  public override void PrintStatus() in Derived1
//  public virtual void PrintStatus() in Base

从输出结果中很容易看出 Derived2 并没有按照我们期望的去做。但你无需惊讶,这是由于 Derived2 的设计者没有“遵守约定”的缘故。

3. new:封印咒术

new 似乎给人一种这样的感觉,它的使用者喜欢打破别人的约定,然而,如果使用恰当,new 可以弥补基类设计者的“短见”。在 Creating a Data Bound ListView Control 中,Rockford Lhotka 就示范了如何封印原来的 ListView.Columns,并使自行添加的返回 DataColumnHeaderCollection 的 Columns 取而代之。

从 Output #01 中我们可以看到,new 只是把 Base.PrintStatus 封印起来而不是消灭掉,你可以解除封印然后进行访问。对于 Derived2 的使用者,解封的方法是把 Derived2 的实例转换成 Base 类型:

//  Code #06

Base d2  =   new  Derived2();
d2.PrintStatus();

//  Output #02

//  public virtual void PrintStatus() in Base

而在 Derived2 内部,你可以透过 base 来访问:

//  Code #07

base .PrintStatus();

这种方法是针对实例成员的,如果被封印的成员是静态成员的话,就要透过类名来访问了。

4. 假如 Base.PrintStatus 是某个接口的隐式实现...

假如 Base 实现了一个 IFace 接口:

//  Code #08

interface  IFace
{
     void  PrintStatus();
}

class  Base : IFace
{
     public   virtual   void  PrintStatus()
     {
        Console.WriteLine( " public virtual void PrintStatus() in Base " );
    }
}

我们只需要让 Derived2 重新实现 IFace:

//  Code #09

class  Derived2 : Base, IFace
{
     public   new   void  PrintStatus()
     {
        Console.WriteLine( " public new void PrintStatus() in Derived2 " );
    }
}

Derived1 保持不变。则把:

//  Code #10

IFace[] fs  =   new  IFace[]
{
     new  Base(),
     new  Derived1(),
     new  Derived2(),
}

传递给:

//  Code #11

public   void  DisplayStatusOf(IFace[] fs)
{
     foreach  (IFace f  in  fs)
     {
        f.PrintStatus();
    }
}

的输出结果是:

//  Output #03

//  public virtual void PrintStatus() in Base
//  public override void PrintStatus() in Derived1
//  public new void PrintStatus() in Derived2

从输出结果中,我们可以看到,虽然 Derived2.PrintStatus 应用了 new,但却依然参与动态绑定,这是由于 new 只能割断 Derived2.PrintStatus 和 Base.PrintStatus 的联系,而不能割断它与 IFace.PrintStatus 的联系。我在 Derived2 的定义中重新指定实现 IFace,这将使得编译器认为 Derived2.PrintStatus 是 IFace.PrintStatus 的隐式实现,于是,在动态绑定时 Derived2.PrintStatus 就被包括进来了。

5. 谁的问题?

我必须指出,如果 Base(Code #01)和 Derived2(Code #04)同时存在的话,它们俩其中一个存在着设计上的问题。为什么这样说呢?Base 的设计者在 PrintStatus 上应用 virtual 说明了他希望派生类能透过重写这一方法来参与动态绑定,即多态性;而 Derived2 的设计者在 PrintStatus 上应用 new 则说明了他希望割断 Derived2.PrintStatus 和 Base.PrintStatus 之间的联系,这将使得 Derived2.PrintStatus 无法参与到 Base 的设计者所期望的动态绑定中。如果在 Base.PrintStatus 上应用 virtual(即对多态性的期望)是合理的话,那么 Derived2.PrintStatus 应该换用另外一个名字了;如果在 Derived2.PrintStatus 上应用 new(即否决参与动态绑定)是合理的,那么 Base.PrintStatus 应该考虑是否去掉 virtual 了,否则就会出现一些奇怪的行为,例如 Output #01 的第三行输出。

假如继承体系中多态性行为的期望是合理的话,那么更实际的做法应该是把 Base 定义成这样:

//  Code #12

abstract   class  Base
{
     public   abstract   void  PrintStatus();
}

而原来 Base 中的实现应该下移到一个派生类中:

//  Code #13

class  Derived3 : Base
{
     public   override   void  PrintStatus()
     {
        Console.WriteLine( " public override void PrintStatus() in Derived3 [originally implemented in Base] " );
    }
}

这样,Derived2.PrintStatus 将使得编译无法完成,从而迫使其设计者要么更改方法的名字,要么换用 override 修饰。这种强制使得 Derived2 的设计者不得不重新考虑其设计的合理性。

假如继承体系中多态性行为的期望不总是合理呢?例如 Stream 有这样一个方法:

public   abstract   long  Seek( long  offset, SeekOrigin origin);

现在假设我有一个方法在处理输入流时需要用到 Stream.Seek:

//  Code #14

public   void  Resume(Stream input,  long  offset)
{
     //  

    input.Seek(offset, SeekOrigin.Begin);

     //  
}

当我们向 Resume 传递一个 NetworkStream 的实例,Resume 将会抛出一个 NotSupportedException,因为 NetworkStream 不支持 Seek。那么这是否说明 Stream 的设计有问题呢?

设想 Resume 是一个下载工具进行断点续传的方法,然而,并不是所有的服务器都支持断点续传的,于是,你需要首先判断输入流是否支持 Seek 操作,再决定如何处理输入流:

//  Code #15

public   void  Resume(Stream input,  long  offset)
{
     if  (input.CanSeek)
     {
         //  

        input.Seek(offset, SeekOrigin.Begin);

         //  
    }
     else
     {
         //  
    }
}

如果 CanSeek 为 false,那就只好从头来过了。

实际上,我们并不能保证任何 Stream 的派生类都能够支持某个(些)操作,我们甚至不能保证来自同一个派生类的所有实例都支持某个(些)操作。你可以设想有这样一个 PriorityStream,它能够根据当前登录账号的权限来决定是否提供写操作,这使得拥有足够权限的人才能修改数据。或许 Stream 的设计者已经预料到这类情况的发生,所以 CanRead、CanSeek 和 CanWrite 就被加入到 Stream 里了。

值得注意的是,Code #07 的 Derived2 可能是一个很糟糕的设计,也可能是一个很实用的设计。在本文,它是一个很糟糕的设计,如果你足够细心,你会察觉到 Derived2 的设计者希望 Derived2.PrintStatus 绕过 Base.PrintStatus 而直接和 IFace.PrintStauts 进行关联,表面上这没什么不妥,但实质上 Base.PrintStatus 和 IFace.PrintStauts 在约定上是同质的,这意味着如果与 IFace.PrintStauts 进行关联就等于承认自己和 Base.PrintStatus 是同质的,这样的话,为什么不直接在 Derived2 里重写 PrintStatus 呢?在《基类与接口混合继承的声明问题》中,我示范了一个实用的设计,用 new 和接口重新实现(Interface reimplementation)来纠正非预期的多态行为。

6. 最后...

当我的朋友拿着问题来找我时,我通常都不会直接给出我的答案,而是尽我的能力向他提供足够多的可用信息,以便他能够根据他所面临的实际情况作出处理,毕 竟,我不会比他更了解他的问题,而他也应该形成他自己的关于他的问题的思考。我希望浪子能用自己的答案回答他所提出的问题,因为只有这样,那些知识才真正 属于他,并且我也相信本文已经提供了足够多的可用信息。

原文URL:http://www.cnblogs.com/allenlooplee/archive/2006/03/13/348760.html

转载于:https://www.cnblogs.com/jeriffe/articles/1421098.html

【转】多态与 new [C#]相关推荐

  1. 重拳出击之《JVM》面试官版 (初哥勿看)

    <fonr color = black>JVM发展史,虚拟机发展史模块 java技术体系包括了几个组成部分? javaME.SE.EE分别是什么? 都说JDK7版本是第一个里程碑版本,为什 ...

  2. Python Day26:多态、封装、内置函数:__str__、__del__、反射(反省)、动态导入模块...

    ## 多态 ```python OOP中标准解释:多个不同类型对象,可以响应同一个方法,并产生不同结果,即为多态 多态好处:只要知道基类使用方法即可,不需要关心具体哪一个类的对象实现的,以不变应万变, ...

  3. Go 学习笔记(36)— 基于Go方法的面向对象(封装、继承、多态)

    Go 面向对象编程的三大特性:封装.继承和多态. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式 继承:使得子类具有父类的属性和方法或者重新定义.追加属性和方法等 多态:不同对象中同种行为的不 ...

  4. C#关于面对象多态例子

    //主的喂狗 class Program     {         static void Main(string[] args)         {             //我们来模拟一个主人 ...

  5. java为什么序列化不一致_java – 为什么Jackson多态序列化在列表中不起作用?

    杰克逊正在做一些真正奇怪的事情,我找不到任何解释.我正在进行多态序列化,当一个对象独立时它可以很好地工作.但是,如果将相同的对象放入列表并对列表进行序列化,则会删除类型信息. 它丢失类型信息的事实将导 ...

  6. 【C++】多态(早期绑定、后期绑定)、抽象类(纯虚函数)、虚析构函数

    我们都知道面向对象编程的三大特征是封装.继承.多态,今天我们就来说一下其中之一的多态. 概念: 多态: 多态字面意思就是多种形态,C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同 ...

  7. Go 分布式学习利器(13)-- Go语言的多态

    文章目录 1. 基本的多态实现 2. 空接口与断言 3. Go接口的最佳实践 1. 基本的多态实现 我们知道C++中实现多态是通过虚函数表 和 继承来 实现的. 类似如下代码: class Progr ...

  8. (1)访问控制 (2)final关键字 (3)对象创建的过程 (4)多态

    1.访问控制(笔试题) 1.1 常用的访问控制符 public - 公有的 protected - 保护的 啥也不写 - 默认的 private - 私有的 1.2 访问控制符的比较 访问控制符 访问 ...

  9. 多态---父指针指向子类对象(父类引用指向子类对象)

    我们都知道,面向对象程序设计中的类有三大特性:继承,封装,多态,这个也是介绍类的时候,必须提到的话题,那么今天就来看一下OC中类的三大特性: 一.封装 封装就是对类中的一些字段,方法进行保护,不被外界 ...

  10. C++拾趣——使用多态减少泛型带来的代码膨胀

    泛型编程是C++语言中一种非常重要的技术,它可以让我们大大减少相似代码编写量.有时候,我和同事提及该技术时,称它是"一种让编译器帮我们写代码的技术".(转载请指明出于breakso ...

最新文章

  1. 《大道至简》读后感(伪代码)
  2. C++之 static 关键字
  3. 九章算法 | 骑士的最短路线-BFS
  4. server.xml拒绝访问 无法修改
  5. 模板变量,过滤器和静态文件引用
  6. 理解正确的日志输出级别
  7. java加载js_Java加载js
  8. 2nd scrum站立会议
  9. C语言动态链表数据结构实现的学生信息项目
  10. mybatis批量导入
  11. Android Studio 2.2 正式稳定版已发布,先睹为快!
  12. 悉尼大学 GC in Data Science 学习总结
  13. (python)bing搜索引擎API接入测试
  14. 新手入门linux必看
  15. 第二阶段>>>数据库/SQL/SSM/JDBC/核心总结
  16. R语言-股票数据库(4)-股票行业和概念板块数据-Wind
  17. Invalid bound statement (not found)错误的可能原因
  18. 聊聊Stata中的profile文件
  19. 安卓悬浮窗口,  丝滑双指缩放视频窗口
  20. Arduino光敏传感器控制LED灯亮度

热门文章

  1. 家里的狗为什么打不过猫
  2. mysql 锁表 for update,MySQL中select * for update锁表的问题(转)
  3. 非确定性算法_《长安十二时辰》背后的文娱大脑:如何提升爆款的确定性?
  4. 25.212---复用和信道编码
  5. Spring IOC核心源码学习
  6. UVA 1213 Sum of Different Primes
  7. 乌鲁木齐高新区大数据产业首个惠农项目落地
  8. 微信公众号小程序开发
  9. jsp+servlet实现模糊查询和分页效果
  10. 什么是领域模型(domain model)?贫血模型(anaemic domain model) 和充血模型(rich domain model)有什么区别...