Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15515581
  • 博文数量: 112
  • 博客积分: 11195
  • 博客等级: 上将
  • 技术积分: 1989
  • 用 户 组: 普通用户
  • 注册时间: 2005-06-20 11:04
文章分类

全部博文(112)

文章存档

2013年(2)

2012年(27)

2011年(6)

2010年(11)

2009年(6)

2007年(7)

2006年(23)

2005年(30)

分类: C/C++

2006-08-08 13:35:08

条款2:
多态
多态(polymorphism)在一些编程教材中被弄得很神秘,而在另外一些教程中则被忽
略,其实它不过是C++语言所支持的一个简单而有用的概念。按照C++标准所言,“多态类
型”旧时代由虚函数的类类型(class type)。从设计的角度来看,“多态对象”就是一个具
有不止一种类型的对象,而“多态基类”则是一个为满足多态对象的使用需求而设计的基
类。
 让我们看一个金融期权的类型AmOption,如图1所示。

   +-------------------------------------------------------+

   |             Deal                   iceable                                  |

   |              ^                          ^                                        |

   |              |                            |                                        |

   |         +------------------------------+                         |

   |          |                     Option               |                          |

   |         +------------------------------+                         |   

   |             ^                           ^                                        |

   |             |                             |                                        |

   |          AmOption        EurOption                                  |

   +------------------------------------------------------------------+
        图1 在一个金融期权层次结构中多态的作用。AmOption有4种类型
 
 
 

          
 AmOption对象同时具有四种类型:AmOption\Option\Deal\Priceable。由于一个类
型是一组操作,因此,AmOption对象通过其4个接口中的任何一个进行操纵。这意味着一
个AmOption对象可以被针对Deal\Priceable\Option接口编写的代码所操纵,从而允许
AmOption的实现利用或复用所有那些代码。对于AmOption这样的多态类型,从基类继承
的最重要的东西就是他的接口,而不是他的实现。事实上,一个基类仅仅由接口组成不
但常见,而且通常正是我们所希望的。
 
 当然,这里还有一个需要注意的地方。如果让这种优势能够发挥出来,一个良好设计
的多态类对于他的每个基类而言必须是可替换的。换句话说,如果针对Option接口编写的
通用代码接受的是一个AmOption对象,那么该对象的行为最好就像一个Option对象。
 这并不是说AmOption对象应该和Option对象的行为完全一致(Option可能有很多纯虚函
数)。实际上一个多态基类想象成一份合同更好理解一些。这个基类对其借口的用户作了
某些承诺,这些承诺包括郑重的语法承诺,即约定的成员函数可以通过一些特定类型的实
参进行调用,以及不容易验证的语义上的承诺,即当一个特定的成员函数被调用时将会发
生什么实际情况。像AmOption和EurOption这样的具体派生类被称为“转包者”,他们实现
Option与其客户签订的合同,如图2所示。

                                    +--------------------------------+

  +--------------+          |class Option                              |

  |针对Option    |           |{                                               |

  |接口编写的  |< --> |    virtual price()=0;                    |

  |代码               |          |    update();                                |

  +--------------+         |};                                               |

                                     |---------------------------------+

                                     |class AmOption:public Option     |

                                     |{                                                 |

                                     |    virtual price();                          |

                                     |};                                                |

                                     |----------------------------------+

                                     |class EurOption:public Option      |

                                     |{                                                  |

                                     |    virtual price();                           |

                                     |};                                                 |

                                     +----------------------------------------+
     图2,一个多态的承包者及其“转包者”
                                        
 举了例子,如果Option具有一个纯虚成员函数Price,其作用是给出Option的当前值,
那么AmOption和EurOption都必须实现这个函数。我们显然不会为这两种类型的Option实
现完全一致的行为,但他们都应该计算并返回一个价格(price),而不应该去拨打一个电
话或者去玩游戏:)
 
 另一方面,如果我们要去访问同一个对象的两种不同接口的price函数,那么我们应
该得到相同的结果。就本质而言,每一个调用都应该绑定到同一个函数:
    AmOption *d=new AmOption;    
    Option *b=d;
    d->price();
    b->price();  //其实就是说虚函数实际上还是调用的AmOption的。      
                                                    
    这是有意义的。假如我问你“那个美国期权的当前值是什么?”,我期望得到与下面
简短提问方式相同的答案:“那个期权的当前值是什么?”
 当然,同样的推理也适用于对象的非虚函数:
 b->update();
 d->update();   //都是调用的b里面的update
 
 正是基于提供的合同允许针对基类借口编写的“多态”代码对特定的合同起作用,同时
有助于对派生类的存在保持“健康的不知情”。换句话说,多态代码可能正在操作AmOption
和EerOption对象,但除非特别关心他们到底是什么对象,否则均被视为Option对象。各
种各样“具体的”Option类型可以被添加或删除而不会影响到只关心基类Option的通用代码。
比方说,如果某一个地方出现一个AsianOption对象,那么只知道Option的多态代码也能
操作它,这全托"对具体类型不知情"的福,如果以后这个对象消失了,也犯不着去挂念它。
 出于同样的原因,像AmOption和EurOption这样具体的期权类型只需要知道积累就可
以了,改变通用代码对他们毫无影响。原则上,基类可以不知道出自身以外的任何事物。
从实践的角度来看,对其接口的设计要考虑与其用户的需求,并且应该以这样的方式进
行设计:派生类很容易的推知并实现其合同。然而,基类应该对其派生类的具体细节全
然不知,因为知道这些会不可避免的致使类层次结构上添加或删除派生类变得困难。
 和生活中一样,在面向对象设计中,“不知情”或“忽略”也是天赐之福。                          
 
ps:真郁闷,两个图真不好弄~~
阅读(42640) | 评论(3) | 转发(0) |
给主人留下些什么吧!~~