-1. 目录

  • -1. 目录
  • 0. 写在前面的话。
    • 0.0 关键字。
    • 0.1 系统要求?!
    • 0.2 如何阅读本文?
  • 1. 图书馆魔术事件簿。
    • 1.0 图书管理员的烦恼。
    • 1.1 魔术棒是如何工作的?
    • 1.2 魔术般真能起作用吗?
    • 1.3 我们在干什么?
    • 1.4 Poly呢?
  • 2. 多态为何物?
    • 2.0 真实的多态。
    • 2.1 多态的种类。
    • 2.2 多态失效了?
    • 2.3 要睡觉了吗?
  • 3. 多态与重构。
    • 3.0 请别妨碍我们改善公司的组织结构!
    • 3.1 Poly的噩梦——图书馆的倒塌。
  • 4. 完结·新篇。
  • 5. 参考书目。
  • X. 注释。

 

0. 写在前面的话。

0.0 关键字。

  • 中文:多态,面向对象,继承,封装,抽象类,重构,基类,基类继承多态,接口,接口继承多态,抽象方法,虚方法,重载方法,继承体系,插件系统,引用类型,值类型,接口多重继承
  • 英文:Polymorphism, Object-Oriented, Inheritance, Encapsulation, abstract class, base class, Refactoring, interface, abstract method, virtual method, overriden method, override, new, Null Object Pattern, reference type, value type, switch

0.1 系统要求?!

  • 0.1.0 使用面向对象技术(至少有使用的打算)。
  • 0.1.1 对C#有一个基本的了解(我指的是语法以及相关的概念)。
  • 0.1.2 初步了解面向对象的继承以及封装(如果没有了解...就看着办吧!)。
  • 0.1.3 希望了解多态及其相关的内容。
  • 0.1.4 必要的耐性(至少你应该看完“如何阅读本文?”。连“如何阅读本文?”都看不进?关掉浏览器吧!)。
  • 0.1.5 其它......

0.2 如何阅读本文?

【转】今天你多态了吗?switch(you.Purpose)
}

 

1. 图书馆魔术事件簿[1]。

1.0 图书管理员的烦恼。

  • Poly:学校这几年扩招,图书馆也有一大批新书入库,而管理员却还是只有我们两个,每天都忙个不停。
  • Morph:没办法啦,这总比失业好吧?
  • Poly:哎,每天单处理归还的书的工作就叫人喘不了气了,如果这些书能够自动飞回各自所属的位置该多好呀!
  • Morph:来,我赐你一把魔术棒,这样你就不用那么辛苦了。
  • Poly:拜托!不要拿支牙刷来耍我,今天的工作恐怕又干不完了,没空跟你开玩笑呀!
  • Morph:呵呵,谁叫你有这种闲情发白日梦?

1.1 魔术棒是如何工作的呢?

刚刚才被Morph耍完的Poly现在又在发白日梦了,他想着Morph提到的魔术棒,自言自语:“如果真的有这么一支魔术棒,它将如何工作呢?”

他联想到自己平时工作的情况,认为要把图书归类好,必须满足以下两个条件:

  • a) 每本图书都应该具有一些信息,这些信息描述了该书的所属类别以及存放地点等;
  • b) 有一头任劳任怨的牛根据这些信息把书拿到存放地点放好。

想着想着,Poly奏起眉头了:“这么说来,我不就是那头牛?不行,我得让魔术棒帮帮我!”于是,Poly又陷入沉思了:如果这些书懂得自己飞回它所属的存放地点的话......对了,得让魔术棒帮我这个忙!

1.2 魔术棒真能起作用吗?

1.2.0 现实中的Poly是如何工作的?

【转】今天你多态了吗?class Poly
}

1.2.1 Poly,我终于理解你的呐喊了!

说实话,直道我完成这个代码,我才真正明白为何Poly一 直在烦闷,我想,这个代码应该可以令校方领导下决心改善图书馆的工作环境,至少也得请多几个人来分担一下工作。否则,某天学校突然决定要为图书馆加楼层、 增加新的图书类别或者调整图书现有类别时,Poly会毅然决定逃去参军(失业大军)!不信,你试着把第二书店的电脑书部分分类加入到图书馆的4楼,合并3 楼现有的类别,并增加文学、宗教等系列的新类别,然后......怎么样?有什么感觉?牵一发而动全身!

1.2.2 Poly决定挥动手上的魔术棒:

Poly手上的魔术棒叫什么名字呢?既然是Morph“赐”的,就叫他Morphism吧!好了,Poly挥动他手上的Morphism魔术棒...

【转】今天你多态了吗?class Polymorphism
}

下面是魔术棒所产生的副产品[2]

【转】今天你多态了吗?

1.2.3 检验魔术棒的效果。

1.2.3.0 加入新电脑书籍分类。

【转】今天你多态了吗?class WebDevelopmentBook : Floor4Book
}

当然,你需要在4楼腾出地方来放一个(或多个)新的书架来安放Web Development类的书籍。

1.2.3.1 合并3楼现有的分类。

【转】今天你多态了吗?class BusinessBook : Floor3Book
}

当然,你需要把Economics、Marketing、Management三类书的书架放在一起(或者你有更好的是这些书放在一起的方法),把原本书架的这三个标签撕掉,重新贴上Business标签。

1.2.3.2 学校要加盖第五层楼?

{ }

当然,你需要为5楼添加设施和设定图书分类,并放入一些书。

1.3 我们在干什么?

实质上我们在重构(Refactoring)!只是前后两种代码哪种来的更清晰以及更可读而已。当然,在这个重构的过程中,我们很难避免改动图书馆(Library)以及各层楼的公共设施(公共成员),所以,必须小心行事!

说实话,这并不是一个好的例子,因为书本的行为与图书馆结构和设施的细节有太多的藕断丝连了。所以,如果我们能够为他们找一个中间人(第三者?)来处理这些复杂关系(别说我坏心肠棒打鸳鸯呀!),避免过分的纠缠就好了。不过,作为一个开场白,我想这应该够了吧!?

1.4 Poly呢?

  • Poly:“哎呀!好疼哟!谁用书敲我的头?”
  • Morph:“你真过分,居然偷懒在这里大睡?”
  • Poly:“魔术棒...”
  • Morph:“什么魔术棒?你拿着这支牙刷干什么吗?”
  • Poly:“......”

 

2. 多态为何物?

2.0 真实的多态。

我曾经在《readonly vs. const》一文提到下面这句话:

然而,当这种结合使用枚举和条件判断的代码阻碍了你进行更灵活的扩展,并有可能导致日后的维护成本增加,你可以代之以多态,使用Replace Conditional with Polymorphism来对代码进行重构

下面我来试一下用多态实现会员分级制,首先我们来看看UML类图:

【转】今天你多态了吗?

接着看看对应的代码:

【转】今天你多态了吗?class Order
}

这样,如果日后我们发现业务需要,要添加一个InactiveCustomer身份来表示那些很久没有上来购物的人,我们就可以:

【转】今天你多态了吗?class InactiveCustomer : Customer
}

然而,Order类却没有受到任何的影响,这才是最重要的!当然,如果因为某些原因,我们决定去掉SuperVip这个身份,也将看到不会对Order产生任何影响!这,就是多态的威力,刚才发生在图书馆的一切...忘了吧!

2.1 多态的种类。

2.1.0 基类继承多态(Base Class Polymorphism)

现在让我们回到网上商店,我们首先创建了一个 Customer的abstract class,然后,在Order里面使用这个Customer的“实例”(在Order的构造函数进行赋值初始化)。然而,我们知道一个abstract class是不可能被实例化的,那么我们究竟在引用着什么呢?答案是Customer的派生类的实例(这个派生类就不能够再是abstract的了)。由 于每一个Customer的派生类跟之均是一种is-a的关系,这些派生类必定能够执行Customer约定的功能,从而只要Order知道 Customer提供什么功能,就能够应付这些派生类里同样的功能了[3]。当然,如果我们在派生类里面添加了一些新的功能,那么这些新的功能将不会被Order识别而已,因为Order不能透过Customer的约定来获知这些新的功能的存在。

使用基类继承多态的关键就是有一个继承体系(如果没有,就建立一个)[4],而客户端只需要持有一个类型为这个体系的顶层基类[5]的变量,用于保存其派生类的实例,并调用预先约定的抽象方法或者虚方法就行了,剩下的事多态将会为你好好的安排!

通常我们在面对一组相关的对象时,我们就会考虑使用多态。请留意Order.TotalAmount()的代码片断:

【转】今天你多态了吗?foreach(Product product in m_Products)
【转】今天你多态了吗?  totalAmount 
+= product.Price;

这里的Product可能是一个基类(抽象或者非抽象),它的派生类可能有:Book、Toy、Movie、CD、CPU等等,然而,我们需要一种统一的方法来把一组产品的价格加总。使用多态,就可以避免询问变量的类型在判断并读取所需的数据。

2.1.1 接口继承多态(Interface Polymorphism)

除了基类继承多态,我们还有一种接口继承多态。顾名思义,这种多态是通过继承(更确切的说是“实现”)接口而产生继承体系的。所以,使用接口继承多态的关键也是拥有一个继承体系。一般情况下我们会尽量使用基类继承多态[6],其原因可能是由于关系表述的准确性(is-acan-do之间概念上的区别[7]),或者版本控制的问题。然而,接口继承多态仍然有它独特的用处,当一个对象需要拥有不同的身份时,接口继承就给了你一种实现的方式。例如String的声明如下:

【转】今天你多态了吗?public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>

这样,String就可以以多种不同的身份出席不同的场合了,例如

【转】今天你多态了吗?System.Collection.SortedList.Add(object key, object value);

要求传递给该方法的key参数必须实现IComparable接口,以便能够进行排序比较。

换句话说,C#不支持基类多重继承,却支持接口多重继承。

2.1.2 混合继承多态?

由于一个类可以同时继承一个基类(base class)、实现多个接口(interface),我们不难想象一下声明:

【转】今天你多态了吗?public sealed class DerivedClass : BaseClass, IInterface 【转】今天你多态了吗?

然而,真的有混合继承多态吗?其实是没有的,因为在强类型语言里面,变量在给定的某个时刻只能够以一个身份出现,即使它同时具备了多个身份。你不能够使得一个变量同时是多种类型吧?

2.2 多态失效了?

使用基类继承多态,有一点特别需要注意的就是:基类(抽象或者非抽象)中需要获得多态效果的成员必须有abstractvirtual修饰。例如:

【转】今天你多态了吗?class BaseClass
}

输出结果:

Printed in BaseClass.
Printed in BaseClass.

但是,编译器将给出以下警告:

The keyword new is required on 'DerivedClass.PrintMe()' because it hides inherited member 'BaseClass.PrintMe()'

当我们在BaseClass的PrintMe()前加上virtual时,

【转】今天你多态了吗?public virtual void PrintMe() 【转】今天你多态了吗?

将得到如下警告:

'DerivedClass.PrintMe()' hides inherited member 'BaseClass.PrintMe()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

我们不应该忽略编译器的警告,因为别人看你的代码时,可能会疑惑你的意图,他(或她)可能猜测你是否漏掉了overridenew关键字,又或者在猜测你如果不想继承基类的成员方法,那为什么要为它起同一个名字呢(尤其是你使用new的时候)?所以你不希望继承基类的成员方法,那么最好为方法另起一个名字。你可能有一万个理由表明使用同一个名字的必要,我也没有绝对不可以这样做的意思,只是我们应该尽最大的努力使的代码的维护者一眼就能看出代码的意图而不是做来回的做揣测。

说得太多了,其实这个是继承方面的内容[8],说这些内容主要是希望大家注意程序的输出结果。从结果可以看出,这里并没有发生多态效应!所以我有必要为你重复这一点:

基类(抽象或者非抽象)中需要获得多态效果的成员必须有abstract或virtual修饰。

至于使用哪个修饰符就要看你具体的情况了。

然而,使用接口继承多态就不需要注意这些了,因为所有的接口均为抽象的,你如果要实现(impletement)一个接口,你就必须实现其所有的成员(无论是显式还是隐式)[9],否则编译器将会拒绝编译而不是仅仅给出警告!

2.3 要睡觉了吗?

好了,故事讲完了,但你总不能先个小孩一样,听完故事就上 床睡觉吧?我们总得有个事后思考!或许你已经发现,从头到尾我都没有为多态下一个明确的定义。是的,我没有,也不打算这样做!一个完整精确的定义对我来说 难度太大了,所以我选择用一些例子和相关的解释来为你们描述。不过我还是愿意简单的说一下何谓多态:

多态就是使得你能够用一种统一的方式来处理一组各具个性却同属一族的不同个体的机制。[10]

关于这多态,有一点很关键的,那就是多态是以继承体系为基础的,所以上面这句中的“同属一族”所指的就是“属于同一继承体系的”。觉得烦了吗?还是那一句:忘了它!

 

3. 多态与重构。

3.0 请别妨碍我们改善公司的组织结构!

多态的威力虽然强大,但并不是所有的代码一开始就在该用多态的地方使用多态。面对既有的代码,我们如何重新引爆多态的力量呢?答曰:Replace Conditional with Polymorphism![11]

我借用了《重构》的一段代码[12]

【转】今天你多态了吗?class Employee
}

我们希望公司的薪金系统不会妨碍公司组织结构的改变,我们 可能有上千个职位,每个都有着不同的薪资算法,这些算法又有可能随时发生变化,而且我们还随时有可能新增一个职位或者去掉一个职位,有或者暂增一个临时职 位。我们希望公司组织结构的改动与薪金系统的改动都同样灵活!于是,我们要借助多态的力量了[13]

3.1 Poly的噩梦——图书馆的倒塌。

某天,Poly发了一个梦,梦中Poly和Morph坐在 图书馆一楼大厅里很休闲的听着音乐。原来,魔术榜的效果不但是还书“自动化”,借书也“自动化”了。要借书的话,只需要走进一楼大厅,然后大声叫出书名, 书就会飞到你的手上!如果学校领导知道这件事的话,肯定嚷着要裁掉他们两个!此时,一PLMM来到大厅,大叫一声:今天你多态了吗?突然,Poly和 Morph还有那PLMM感觉到图书馆在震动,而且越来越厉害,最后,图书馆倒塌了!好在他们三个跑得快,否则后果不堪设想。究竟出了什么事?突然那 PLMM尖叫:那墙上刻有“未处理异常:NullReferenctException”!

真实一个可怕的噩梦,但引用空对象在现实中确实家常便饭之事,你必须额外编写代码来处理它[14]

【转】今天你多态了吗?Employee e = paymentSystem.GetEmployee("Allen");
【转】今天你多态了吗?
if(e != null && e.IsTimeToPay(today))
【转】今天你多态了吗?  e.Pay();

或者

【转】今天你多态了吗?Employee e = paymentSystem.GetEmployee("Allen");
【转】今天你多态了吗?
try
}

然而,这些代码看起来都不太漂亮,有没有更统一直观的处理方式呢?答曰:Introduce Null Object[15]

【转】今天你多态了吗?

【转】今天你多态了吗?class Employee
}

实际的操作中,我们为了避免客户端对 NullEmployee的了解,可以把该类作为内嵌类(Nested class)加入到Employee,并提供工厂方法(Factory Method)来生成NullEmployee的实例。并且,我们会将Null Object模式与Singleton模式结合一起使用,因为Null Object对于每一个调用方来说都应该是同质的,也就是一种常量性质的东西,它的成分不应该发生改变。

【转】今天你多态了吗?class NullEmployee : Employee
}

有时候,我们需要对对象进行类似IsNull()的判断并读取里面的值,那么你可以自己声明一个INullable接口,再让NullEmployee实现它。然而,你也可以直接实现.NET(ver. 2.0)内置的System.INullableValue接口:

【转】今天你多态了吗?class Employee : INullableValue
}

然而,Introduce Null Object只能用来处理引用类型(Reference Type),因为值类型(Value Type)都是密封(sealed)的。

 

4. 完结·新篇[16]。

不知不觉到了结论部分,我也不知道该说些什么,但总得留下一些东西。好了,回想一下我对“何谓多态”的回答:

多态就是使得你能够用一种统一的方式来处理一组各具个性却同属一族的不同个体的机制。

再回想一下我们的这些例子,不难发现,我们一直都在缝缝补 补的,我们是修补工吗?是,也不是。说它是,那是因为我们不能轻易放弃既有的代码,要为这些代码做进一步完善;说它不是,那就是如果你一开始就把多态考虑 进你的设计,那么它就不是一项修补工了,当然不排除日后你又需要使用Replace ... with Polymorphism!然而,没有人能够一开始就想出一个完美的设计,所以我们需要重构,而多态,则为我们提供如何去完善我们的设计的基本理念。

那么,如果我们一开始就把多态考虑进我们的设计又会是怎样 一种情况呢?插件系统,如果需要有一点规模的话,这是我能想到的一个答案。那些支持热插拔组件的系统(插件系统)均支持向系统增加外部功能扩展模块(插件 组件)而无需重新编译。然而,系统必须能够识别这些插件组件的功能并执行之才有意义。于是,我们会预先约定一组插件系统能够识别功能接口,并让插件组件实 现这些接口。这样插件系统便能识别并以统一的方式来调用它们。这,不就是多态吗?

当然,插件系统的设计与实现是另一个庞大的课题,里面涉及 的不仅仅是多态,还有其他很多很多的技术,但多态肯定是其核心技术,而且,它还必须遵守开放——封闭原则(The Open-Closed Principle,简称OCP),这样,系统才能够以更灵活的方式去应对未来的变化。作为一个开始,我为你们介绍了多态,剩下的路你们就要拿出自己的探 索精神了。

 原文URL:http://allenlooplee.cnblogs.com/archive/2006/03/10/59519.html

相关文章:

  • 2021-07-28
  • 2021-06-10
  • 2021-07-30
  • 2021-09-01
  • 2021-12-13
  • 2021-07-23
  • 2022-01-15
  • 2022-01-16
猜你喜欢
  • 2021-10-24
  • 2021-12-31
  • 2021-03-31
  • 2021-07-14
  • 2022-01-20
相关资源
相似解决方案