今天你多态了吗?
Do You Polymorphism Today? [0]
Written by Allen Lee
-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)


{
case "希望读一个关于多态的故事“
if(you.IsPatient)
goto "1. 图书馆魔术事件簿";

case "因为《readonly vs. const》的Remark的最后一点而来":
goto "2.0 真实的多态";

case "仅希望了解一下多态的种类":
goto “2.1 多态的种类”

case "仅希望了解一下不同种类的多态的注意事项":
goto “2.2 多态失效了?”

case "只想知道我是如何回答'何谓多态?'":
goto “2.3 要睡觉了吗?”;

case "希望了解多态如何帮助你实现更灵活的条件扩展机制":
goto “3.0 请别妨碍我们改善公司的组织结构”;

case "希望了解Null Object模式以及Introduce Null Object重构原则如何发挥多态的威力":
goto “3.1 Poly的噩梦——图书馆的倒塌”;

case "想听听我对多态的闲谈":
goto “4. 完结·新篇”;

case "只想活动一下因使用鼠标而劳累的手":
goto "关闭你的浏览器";

default:
goto "从头到尾阅读本文";

}
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


{
static void Main()

{
// 图书馆早上准时开门;
Library library = new Library();

// 当然,Poly他们没有理由只开图书馆的大门,他们还要开放图书馆的各层楼。
// 一楼的门就是大门了,要开两次大门吗?
Floor floor2 = library.Floor2;
Floor floor3 = library.Floor3;
Floor floor4 = library.Floor4;
// 同学们一大早就归还很多图书。
List<Book> books = new List<Book>();
// 省略填充books列表的代码
// 

// 我们认为Poly会先把书分类好属于哪一层楼,然后再一次过搬到对应的楼层,
// 而不是傻乎乎的每拿到一本书就跑到对应的楼层并把它放入对应的书架!
List<Book> floor2Car = new List<Book>();
List<Book> floor3Car = new List<Book>();
List<Book> floor4Car = new List<Book>();
// Poly需要根据每本书上的信息进行处理
foreach(Book book in books)

{
// 首先看看该书属于图书馆那一层楼的。
switch(book.FloorNumberString)

{
// 一楼是图书馆办公室和借书柜台,不用于存放图书。
case "2nd":
// 把书放进往二楼的推车里。
floor2Car.Add(book);
break;
case "3rd":
// 把书放进往三楼的推车里。
floor3Car.Add(book);
break;
case "4th":
// 把书放进往四楼的推车里。
floor4Car.Add(book);
break;

// default:
// // 把书放进哪里?地下室?还是天台?
// break;
}
}

// 接着Poly把往二楼的车推到二楼。
// 我们认为Poly会先把书按照所属类别分类好,放在装书篮里,
// 而不是傻乎乎的每拿到一本书就往对应的书架跑。
List<Book> mathBasket = new List<Book>();
List<Book> physicsBasket = new List<Book>();
List<Book> chemistryBasket = new List<Book>();

foreach(Book book in floor2Car)

{
switch(book.Catalog)

{
case "Math":
mathBasket.Add(book);
break;
case "Physics":
physicsBasket.Add(book);
break;
case "Chemistry":
chemistryBasket.Add(book);
break;
}
}

// 然后Poly提着篮子跑到对应的书架
foreach(Book book in mathBasket)
floor2.MathShelves.Add(book)

foreach(Book book in physicsBasket)
floor2.PhysicsShelves.Add(book);

foreach(Book book in chemistryBasket)
floor2.ChemistryShelves.Add(book);

// 接着Poly把往三楼的车推到三楼。
// 我们同样认为Poly会先把书按照所属类别分类好,放在装书篮里,
// 而不是傻乎乎的每拿到一本书就往对应的书架跑。
List<Book> economicsBasket = new List<Book>();
List<Book> marketingBasket = new List<Book>();
List<Book> managementBasket = new List<Book>();

foreach(Book book in floor3Car)

{
switch(book.Catalog)

{
case "Economics":
economicsBasket.Add(book);
break;
case "Marketing":
marketingBasket.Add(book);
break;
case "Management":
managementBasket.Add(book);
break;
}
}

// 然后Poly提着篮子跑到对应的书架
foreach(Book book in economicsBasket)
floor3.EconomicsShelves.Add(book)

foreach(Book book in marketingBasket)
floor3.MarketingShelves.Add(book);

foreach(Book book in managementBasket)
floor3.ManagementShelves.Add(book);

// 接着Poly把往四楼的车推到四楼。
// 如果Poly傻乎乎的每拿到一本书就往对应的书架跑,那我们只好让他跑了。
List<Book> programmingBasket = new List<Book>();
List<Book> seBasket = new List<Book>();
List<Book> databaseBasket = new List<Book>();
List<Book> networkBasket = new List<Book>();

foreach(Book book in floor4Car)

{
switch(book.Catalog)

{
case "Programming":
programmingBasket.Add(book);
break;
case "Software Engineering":
seBasket.Add(book);
break;
case "Database":
databaseBasket.Add(book);
break;
case "Network":
networkBasket.Add(book);
break;
}
}

// 然后Poly提着篮子跑到对应的书架
foreach(Book book in programmingBasket)
floor4.ProgrammingShelves.Add(book)

foreach(Book book in seBasket)
floor4.SEShelves.Add(book)

foreach(Book book in physicsBasket)
floor4.DatabaseShelves.Add(book);

foreach(Book book in chemistryBasket)
floor4.NetworkShelves.Add(book);

// 最后,Poly整个人躺在地上

}
}
1.2.1 Poly,我终于理解你的呐喊了!
说实话,直道我完成这个代码,我才真正明白为何Poly一直在烦闷,我想,这个代码应该可以令校方领导下决心改善图书馆的工作环境,至少也得请多几个人来分担一下工作。否则,某天学校突然决定要为图书馆加楼层、增加新的图书类别或者调整图书现有类别时,Poly会毅然决定逃去参军(失业大军)!不信,你试着把第二书店的电脑书部分分类加入到图书馆的4楼,合并3楼现有的类别,并增加文学、宗教等系列的新类别,然后......怎么样?有什么感觉?牵一发而动全身!
1.2.2 Poly决定挥动手上的魔术棒:
Poly手上的魔术棒叫什么名字呢?既然是Morph“赐”的,就叫他Morphism吧!好了,Poly挥动他手上的Morphism魔术棒...
class Polymorphism


{
static void Main()

{
// 图书馆早上准时开门;
Library library = new Library();

// 怎么每天只需开大门其他楼层的门就自动打开了吗?
// Floor floor1

// 同学们一大早就归还很多图书。
List<Book> books = new List<Book>();
// 省略填充books列表的代码
// 

// 果然每本书自己飞回各自所属的原位哟。
foreach(Book book in books)

{
book.Return(library);
}
}
}

abstract class Book


{
public abstract void Return(Library library);
}


abstract class Floor2Book : Book
{ }


abstract class Floor3Book : Book
{ }


abstract class Floor4Book : Book
{ }

class MathBook : Floor2Book


{
public override void Return(Library library)

{
library.Floor2.MathShelves.Add(this);
}
}

class PhysicsBook : Floor2Book


{
// Code here
}

class ChemistryBook : Floor2Book


{
// Code here
}

class ManagementBook : Floor3Book


{
public override void Return(Library library)

{
library.Floor3.ManagementShelves.Add(this);
}
}

class EconomicsBook : Floor3Book


{
// Code here
}

class MarketingBook : Floor3Book


{
// Code here
}

class ProgrammingBook : Floor4Book


{
public override void Return(Library library)

{
library.Floor4.ProgrammingShelves.Add(this);
}
}

class SoftwareEngineeringBook : Floor4Book


{
// Code here
}

class DatabaseBook : Floor4Book


{
// Code here
}

class NetworkBook : Floor4Book


{
// Code here
}
下面是魔术棒所产生的副产品[2]:

1.2.3 检验魔术棒的效果。
1.2.3.0 加入新电脑书籍分类。
class WebDevelopmentBook : Floor4Book


{
public override void Return(Library library)

{
library.Floor4.WebDevelopmentShelves.Add(this);
}
}
当然,你需要在4楼腾出地方来放一个(或多个)新的书架来安放Web Development类的书籍。
1.2.3.1 合并3楼现有的分类。
class BusinessBook : Floor3Book


{
public override void Return(Library library)

{
library.Floor3.BusinessShelves.Add(this);
}
}
当然,你需要把Economics、Marketing、Management三类书的书架放在一起(或者你有更好的是这些书放在一起的方法),把原本书架的这三个标签撕掉,重新贴上Business标签。
1.2.3.2 学校要加盖第五层楼?

abstract class Floor5Book : Book
{ }
当然,你需要为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


{
public Order(Customer customer)

{
ID = Guid.NewGuid().ToString();
m_Customer = customer;
m_Products = new List<Product>();
}

public readonly string ID;

private Customer m_Customer;

private List<Product> m_Products;
public void AddProduct(Product product)

{
m_Products.Add(product);
}

public double TotalAmount()

{
double totalAmount = 0.0;

foreach(Product product in m_Products)
totalAmount += product.Price;

return totalAmount * m_Customer.Discount;
}

public override string ToString()

{
return "Dear " + m_Customer.Name ",\n" +
"\tTotal amount of order " + ID +
" is " + TotalAmount().ToString();
}
}

abstract class Customer


{

public abstract string Name
{ get; }


public abstract double Discount
{ get; }
}

class SuperVipCustomer : Customer


{
public SuperVipCustomer(string name)

{
m_Name = name;
