今天你多态了吗?

今天你多态了吗?

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 如何阅读本文?

None.gifswitch(you.Purpose)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif  
case "希望读一个关于多态的故事“
InBlock.gif    
if(you.IsPatient)
InBlock.gif      
goto "1. 图书馆魔术事件簿";
InBlock.gif
InBlock.gif  
case "因为《readonly vs. const》的Remark的最后一点而来":
InBlock.gif    
goto "2.0 真实的多态";
InBlock.gif
InBlock.gif  
case "仅希望了解一下多态的种类":
InBlock.gif    
goto “2.1 多态的种类”
InBlock.gif
InBlock.gif  
case "仅希望了解一下不同种类的多态的注意事项":
InBlock.gif    
goto “2.2 多态失效了?”
InBlock.gif
InBlock.gif  
case "只想知道我是如何回答'何谓多态?'":
InBlock.gif    
goto “2.3 要睡觉了吗?”;
InBlock.gif
InBlock.gif  
case "希望了解多态如何帮助你实现更灵活的条件扩展机制":
InBlock.gif    
goto “3.0 请别妨碍我们改善公司的组织结构”;
InBlock.gif
InBlock.gif  
case "希望了解Null Object模式以及Introduce Null Object重构原则如何发挥多态的威力":
InBlock.gif    
goto “3.1 Poly的噩梦——图书馆的倒塌”;
InBlock.gif
InBlock.gif  
case "想听听我对多态的闲谈":
InBlock.gif    
goto “4. 完结·新篇”;
InBlock.gif
InBlock.gif  
case "只想活动一下因使用鼠标而劳累的手":
InBlock.gif    
goto "关闭你的浏览器";
InBlock.gif
InBlock.gif  
default:
InBlock.gif    
goto "从头到尾阅读本文";
InBlock.gif
ExpandedBlockEnd.gif}

 

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是如何工作的?

None.gif class  Poly
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// 图书馆早上准时开门;
InBlock.gif
    Library library = new Library();
InBlock.gif
InBlock.gif    
// 当然,Poly他们没有理由只开图书馆的大门,他们还要开放图书馆的各层楼。
InBlock.gif    
// 一楼的门就是大门了,要开两次大门吗?
InBlock.gif
    Floor floor2 = library.Floor2;
InBlock.gif    Floor floor3 
= library.Floor3;
InBlock.gif    Floor floor4 
= library.Floor4;
InBlock.gif    
InBlock.gif    
// 同学们一大早就归还很多图书。
InBlock.gif
    List<Book> books = new List<Book>();
InBlock.gif    
// 省略填充books列表的代码dot.gif
InBlock.gif    
// dot.gif
InBlock.gif
InBlock.gif    
// 我们认为Poly会先把书分类好属于哪一层楼,然后再一次过搬到对应的楼层,
InBlock.gif    
// 而不是傻乎乎的每拿到一本书就跑到对应的楼层并把它放入对应的书架!
InBlock.gif
    List<Book> floor2Car = new List<Book>();
InBlock.gif    List
<Book> floor3Car = new List<Book>();
InBlock.gif    List
<Book> floor4Car = new List<Book>();
InBlock.gif    
InBlock.gif    
// Poly需要根据每本书上的信息进行处理dot.gif
InBlock.gif
    foreach(Book book in books)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
// 首先看看该书属于图书馆那一层楼的。
InBlock.gif
      switch(book.FloorNumberString)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        
// 一楼是图书馆办公室和借书柜台,不用于存放图书。
InBlock.gif
        case "2nd":
InBlock.gif          
// 把书放进往二楼的推车里。
InBlock.gif
          floor2Car.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "3rd":
InBlock.gif          
// 把书放进往三楼的推车里。
InBlock.gif
          floor3Car.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "4th":
InBlock.gif          
// 把书放进往四楼的推车里。
InBlock.gif
          floor4Car.Add(book);
InBlock.gif          
break;
InBlock.gif
InBlock.gif        
// default:
InBlock.gif        
//  // 把书放进哪里?地下室?还是天台?
InBlock.gif        
//  break;
ExpandedSubBlockEnd.gif
      }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// 接着Poly把往二楼的车推到二楼。
InBlock.gif    
// 我们认为Poly会先把书按照所属类别分类好,放在装书篮里,
InBlock.gif    
// 而不是傻乎乎的每拿到一本书就往对应的书架跑。
InBlock.gif
    List<Book> mathBasket = new List<Book>();
InBlock.gif    List
<Book> physicsBasket = new List<Book>();
InBlock.gif    List
<Book> chemistryBasket = new List<Book>();
InBlock.gif
InBlock.gif    
foreach(Book book in floor2Car)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
switch(book.Catalog)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        
case "Math":
InBlock.gif          mathBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Physics":
InBlock.gif          physicsBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Chemistry":
InBlock.gif          chemistryBasket.Add(book);
InBlock.gif          
break;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// 然后Poly提着篮子跑到对应的书架dot.gif
InBlock.gif
    foreach(Book book in mathBasket)
InBlock.gif      floor2.MathShelves.Add(book)
InBlock.gif
InBlock.gif    
foreach(Book book in physicsBasket)
InBlock.gif      floor2.PhysicsShelves.Add(book);
InBlock.gif
InBlock.gif    
foreach(Book book in chemistryBasket)
InBlock.gif      floor2.ChemistryShelves.Add(book);
InBlock.gif
InBlock.gif    
// 接着Poly把往三楼的车推到三楼。
InBlock.gif    
// 我们同样认为Poly会先把书按照所属类别分类好,放在装书篮里,
InBlock.gif    
// 而不是傻乎乎的每拿到一本书就往对应的书架跑。
InBlock.gif
    List<Book> economicsBasket = new List<Book>();
InBlock.gif    List
<Book> marketingBasket = new List<Book>();
InBlock.gif    List
<Book> managementBasket = new List<Book>();
InBlock.gif
InBlock.gif    
foreach(Book book in floor3Car)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
switch(book.Catalog)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        
case "Economics":
InBlock.gif          economicsBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Marketing":
InBlock.gif          marketingBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Management":
InBlock.gif          managementBasket.Add(book);
InBlock.gif          
break;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// 然后Poly提着篮子跑到对应的书架dot.gif
InBlock.gif
    foreach(Book book in economicsBasket)
InBlock.gif      floor3.EconomicsShelves.Add(book)
InBlock.gif
InBlock.gif    
foreach(Book book in marketingBasket)
InBlock.gif      floor3.MarketingShelves.Add(book);
InBlock.gif
InBlock.gif    
foreach(Book book in managementBasket)
InBlock.gif      floor3.ManagementShelves.Add(book);
InBlock.gif
InBlock.gif    
// 接着Poly把往四楼的车推到四楼。
InBlock.gif    
// 如果Poly傻乎乎的每拿到一本书就往对应的书架跑,那我们只好让他跑了。
InBlock.gif
    List<Book> programmingBasket = new List<Book>();
InBlock.gif    List
<Book> seBasket = new List<Book>();
InBlock.gif    List
<Book> databaseBasket = new List<Book>();
InBlock.gif    List
<Book> networkBasket = new List<Book>();
InBlock.gif
InBlock.gif    
foreach(Book book in floor4Car)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
switch(book.Catalog)
ExpandedSubBlockStart.gifContractedSubBlock.gif      
dot.gif{
InBlock.gif        
case "Programming":
InBlock.gif          programmingBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Software Engineering":
InBlock.gif          seBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Database":
InBlock.gif          databaseBasket.Add(book);
InBlock.gif          
break;
InBlock.gif        
case "Network":
InBlock.gif          networkBasket.Add(book);
InBlock.gif          
break;
ExpandedSubBlockEnd.gif      }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// 然后Poly提着篮子跑到对应的书架dot.gif
InBlock.gif
    foreach(Book book in programmingBasket)
InBlock.gif      floor4.ProgrammingShelves.Add(book)
InBlock.gif
InBlock.gif    
foreach(Book book in seBasket)
InBlock.gif      floor4.SEShelves.Add(book)
InBlock.gif
InBlock.gif    
foreach(Book book in physicsBasket)
InBlock.gif      floor4.DatabaseShelves.Add(book);
InBlock.gif
InBlock.gif    
foreach(Book book in chemistryBasket)
InBlock.gif      floor4.NetworkShelves.Add(book);
InBlock.gif
InBlock.gif    
// 最后,Poly整个人躺在地上dot.gif
InBlock.gif

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

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

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

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

None.gif class  Polymorphism
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// 图书馆早上准时开门;
InBlock.gif
    Library library = new Library();
InBlock.gif
InBlock.gif    
// 怎么每天只需开大门其他楼层的门就自动打开了吗?
InBlock.gif    
// Floor floor1dot.gif
InBlock.gif
InBlock.gif    
// 同学们一大早就归还很多图书。
InBlock.gif
    List<Book> books = new List<Book>();
InBlock.gif    
// 省略填充books列表的代码dot.gif
InBlock.gif    
// dot.gif
InBlock.gif
InBlock.gif    
// 果然每本书自己飞回各自所属的原位哟。
InBlock.gif
    foreach(Book book in books)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      book.Return(library);
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
abstract   class  Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public abstract void Return(Library library);
ExpandedBlockEnd.gif}

None.gif
ExpandedBlockStart.gifContractedBlock.gif
abstract   class  Floor2Book : Book  dot.gif { }
None.gif
ExpandedBlockStart.gifContractedBlock.gif
abstract   class  Floor3Book : Book  dot.gif { }
None.gif
ExpandedBlockStart.gifContractedBlock.gif
abstract   class  Floor4Book : Book  dot.gif { }
None.gif
None.gif
class  MathBook : Floor2Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public override void Return(Library library)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    library.Floor2.MathShelves.Add(
this);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  PhysicsBook : Floor2Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  ChemistryBook : Floor2Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  ManagementBook : Floor3Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public override void Return(Library library)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    library.Floor3.ManagementShelves.Add(
this);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  EconomicsBook : Floor3Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  MarketingBook : Floor3Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  ProgrammingBook : Floor4Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public override void Return(Library library)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    library.Floor4.ProgrammingShelves.Add(
this);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  SoftwareEngineeringBook : Floor4Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  DatabaseBook : Floor4Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  NetworkBook : Floor4Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

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

o_library.GIF

1.2.3 检验魔术棒的效果。

1.2.3.0 加入新电脑书籍分类。

None.gif class  WebDevelopmentBook : Floor4Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public override void Return(Library library)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    library.Floor4.WebDevelopmentShelves.Add(
this);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

1.2.3.1 合并3楼现有的分类。

None.gif class  BusinessBook : Floor3Book
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public override void Return(Library library)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    library.Floor3.BusinessShelves.Add(
this);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

1.2.3.2 学校要加盖第五层楼?

ExpandedBlockStart.gif ContractedBlock.gif abstract   class  Floor5Book : Book  dot.gif { }

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

1.3 我们在干什么?

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

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

1.4 Poly呢?

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

 

2. 多态为何物?

2.0 真实的多态。

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

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

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

o_customer.GIF

接着看看对应的代码:

None.gif class  Order
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public Order(Customer customer)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    ID 
= Guid.NewGuid().ToString();
InBlock.gif    m_Customer 
= customer;
InBlock.gif    m_Products 
= new List<Product>();
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public readonly string ID;
InBlock.gif
InBlock.gif  
private Customer m_Customer;
InBlock.gif
InBlock.gif  
private List<Product> m_Products;
InBlock.gif  
public void AddProduct(Product product)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    m_Products.Add(product);
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public double TotalAmount()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
double totalAmount = 0.0;
InBlock.gif
InBlock.gif    
foreach(Product product in m_Products)
InBlock.gif      totalAmount 
+= product.Price;
InBlock.gif
InBlock.gif    
return totalAmount * m_Customer.Discount;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return "Dear " + m_Customer.Name ",\n" +
InBlock.gif           
"\tTotal amount of order " + ID +
InBlock.gif           
" is " + TotalAmount().ToString();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
abstract   class  Customer
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif  
public abstract string Name dot.gifget; }
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif  
public abstract double Discount dot.gifget; }
ExpandedBlockEnd.gif}

None.gif
None.gif
class  SuperVipCustomer : Customer
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
public SuperVipCustomer(string name)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    m_Name 
= name;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
private string m_Name;
InBlock.gif  
public override string Name
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
get dot.gifreturn m_Name; }
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
private const double c_Discount = 0.65;
InBlock.gif  
public override double Discount
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
get dot.gifreturn c_Discount; }
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return "Name: " + m_Name + "[SuperVip]";
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  VipCustomer : Customer
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
class  NormalCustomer : Customer
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

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

None.gif class  InactiveCustomer : Customer
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
private const double c_Discount = 1.0;
InBlock.gif  
public override double Discount
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
get dot.gifreturn c_Discount; }
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return "Name: " + m_Name + "[Inactive]";
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

然而,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()的代码片断:

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

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

2.1.1 接口继承多态(Interface Polymorphism)

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

None.gif public   sealed   class  String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable < string >

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

None.gif System.Collection.SortedList.Add( object  key,  object  value);

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

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

2.1.2 混合继承多态?

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

None.gif public   sealed   class  DerivedClass : BaseClass, IInterface dot.gif

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

2.2 多态失效了?

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

None.gif class  BaseClass
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
public void PrintMe()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    Console.WriteLine(
"Printed in BaseClass.");
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  DerivedClass : BaseClass
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
public void PrintMe()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    Console.WriteLine(
"Printed in DerivedClass.");
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    BaseClass bc 
= new BaseClass();
InBlock.gif    BaseClass dc 
= new DerivedClass();
InBlock.gif    bc.PrintMe();
InBlock.gif    dc.PrintMe();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

输出结果:

Printed in BaseClass.
Printed in BaseClass.

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

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

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

None.gif public   virtual   void  PrintMe() dot.gif

将得到如下警告:

'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]

None.gif class  Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
int PayAmount()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
switch(GetKind())
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif      
case EmployeeType.ENGINEER:
InBlock.gif        
return _monthlySalary;
InBlock.gif      
case EmployeeType.SALESMAN:
InBlock.gif        
return _monthlySalary + _commission;
InBlock.gif      
case EmployeeType.MANAGER:
InBlock.gif        
return _monthlySalary + _bonus;
InBlock.gif      
default:
InBlock.gif        
throw new InvalidOperationException("Incorrect Employee");
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

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

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

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

None.gif Employee e  =  paymentSystem.GetEmployee( " Allen " );
None.gif
if (e  !=   null   &&  e.IsTimeToPay(today))
None.gif  e.Pay();

或者

None.gif Employee e  =  paymentSystem.GetEmployee( " Allen " );
None.gif
try
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
if(e.IsTimeToPay(today))
InBlock.gif    e.Pay();
ExpandedBlockEnd.gif}

None.gif
catch (NullReferenceException)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Exception handler codedot.gif
ExpandedBlockEnd.gif
}

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

o_nullobject.GIF

None.gif class  Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
public virtual bool IsTimeToPay(DateTime dt)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// dot.gif
ExpandedSubBlockEnd.gif
  }

InBlock.gif
InBlock.gif  
public virtual void Pay()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// dot.gif
ExpandedSubBlockEnd.gif
  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  NullEmployee : Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  
public override bool IsTimeToPay(DateTime dt)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// 空方法体!
ExpandedSubBlockEnd.gif
  }

InBlock.gif
InBlock.gif  
public override void Pay()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
// 空方法体!
ExpandedSubBlockEnd.gif
  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    Employee e 
= paymentSystem.GetEmployee("Allen");
InBlock.gif    
// 这样,如果GetEmployee("Allen");返回一个空引用,下面的代码将什么也不做!
InBlock.gif
    if(e.IsTimeToPay(DateTime.Today)
InBlock.gif      e.Pay();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

None.gif class  NullEmployee : Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
private static NullEmployee m_Instance = null;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif  
private NullEmployee() dot.gif{ }
InBlock.gif
InBlock.gif  
public static NullEmployee CreateInstance()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
if (m_Instance == null)
InBlock.gif      m_Instance 
= new NullEmployee();
InBlock.gif
InBlock.gif    
return m_Instance;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
// Code heredot.gif
ExpandedBlockEnd.gif
}

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

None.gif class  Employee : INullableValue
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  INullableValue.HasValue()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return true;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  INullableValue.Value()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return this;
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
class  NullEmployee : Employee
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// Code heredot.gif
InBlock.gif

InBlock.gif  INullableValue.HasValue()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return false;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  INullableValue.Value()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif    
return null;
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

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

 

4. 完结·新篇[16]。

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

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

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

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

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

 

5. 参考资料。

  • Martin Fowler; Refactoring: Improving the Design of Existing Code; Addison Wesley Longman, Inc., 1999
  • Robert C. Martin; Agile Software Development: Principles, Patterns, and Practices; Pearson Education, Inc., 2003
  • Alan Shalloway, James R. Trott; Design Patterns Explained: A New Perspective on Object-Oriented Design; Addison Wesley, 2002
  • Jesse Liberty; Programming C#, 3rd Edition; O'Reilly & Associates, Inc., 2003
  • Jeffrey Richter; Applied Microsoft .NET Framework Programming; Microsoft Press, 2002
  • Don Box, Chris Sells; Essential .NET, Volume 1: The Common Language Runtime; Addison Wesley, 2002
  • Eric Gunnerson; A Programmer's Introduction to C#; Apress, 2000
  • Microsoft Corporation; .NET Framework SDK Documentation; Microsoft Corporation

 


X. 注释。

  1. 本文的题目形式借用了当年Yahoo! 的标语“Do You Yahoo?”。
  2. 本故事纯属虚构,如有雷同,纯属巧合。
  3. 本UML类图是使用Microsoft Visio绘制的。
  4. 这里的“同样的功能”并不是指有着一样的执行效果的方法(method),而仅仅是签名(signature)相同(除abstract、virtual、override这三个关键字)的方法。
  5. 继承是另一个面向对象的重要概念,关于继承及如何建立继承体系的内容可以写一片完整的文章了,所以请另行查阅相关资料。
  6. 该基类不一定都是抽象类(abstract class),只要不为密封类(sealed class)就行了。
  7. 有关使用基类还是接口来建立继承体系的更详细内容,可以另行查阅相关资料。
  8. 基类继承是is-a关系,接口继承是can-do关系。
  9. 如果想更深入了解相关的内容,可以查阅有关继承体系中成员的版本控制方面的资料。
  10. 有关接口的使用,可以查阅.NET文档或者MSDN在线文档。
  11. 注意,请别将这当作多态的定义,它仅仅是我对“何谓多态”的简单回答。
  12. 由于本文的重点并不是重构,有关该代码的重构过程、结果以及相关注意事项,请参考《重构——改善既有代码的设计》的9.6 Replace Conditional with Polymorphism。
  13. 代码原样请参见《重构——改善既有代码的设计》P.257,此处代码稍有改动。
  14. 其实要应对这些变化,单纯的多态是不够的,你还要把存在变化风险的部分(例如可能随时发生改变的薪资算法)封装好,让调用方不能直接接触它,那么如果它发生了改变就不会波及到调用方,因为调用方从头到尾都不知道它是如何工作的!当然,这里的一个关键就是处理好公共接口。关于封装,它也是面向对象的一个重要概念,并且其内容也很庞大,详细了解请另行查阅相关资料。
  15. 该代码改编自《敏捷软件开发 原则、模式与实践》的第17章Null Object模式的示例。
  16. 关于Introduce Null Object重构原则详细步骤以及注意事项,请参见《重构——改善既有代码的设计》的9.7 Introduce Null Object。
  17. 由于本小节并不能算是真正意义上的结论总结,只是一个本文阅读完成与读者实践开始的交界点,所以我使用这样一个标题。当然,一开始我只知道用“Endings and Beginnings”能确切表达这种意思,却找不到一个对应的合适的中文标题,本标题是我与Teddy Tam共同讨论的结果。当然Teddy的功劳最大,他绞尽脑汁的想出一个又一个的标题,然后我只是在一旁“感觉”一下这些标题是否合适。所以在这里要再次感谢Teddy的支持。

 

转载于:https://www.cnblogs.com/allenlooplee/archive/2004/11/02/59519.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值