条款39:避免“向下转换”继承层次.doc

上传人:壹****1 文档编号:560550997 上传时间:2023-06-02 格式:DOC 页数:12 大小:39.50KB
返回 下载 相关 举报
条款39:避免“向下转换”继承层次.doc_第1页
第1页 / 共12页
条款39:避免“向下转换”继承层次.doc_第2页
第2页 / 共12页
条款39:避免“向下转换”继承层次.doc_第3页
第3页 / 共12页
条款39:避免“向下转换”继承层次.doc_第4页
第4页 / 共12页
条款39:避免“向下转换”继承层次.doc_第5页
第5页 / 共12页
点击查看更多>>
资源描述

《条款39:避免“向下转换”继承层次.doc》由会员分享,可在线阅读,更多相关《条款39:避免“向下转换”继承层次.doc(12页珍藏版)》请在金锄头文库上搜索。

1、条款39:避免“向下转换”继承层次条款39: 避免 向下转换 继承层次在当今喧嚣的经济时代,关注一下我们的金融机构是个不错的主意。所以,看看下面这个有关银行帐户的协议类(Protocol class )(参见条款34):class Person . ;class BankAccount public: BankAccount(const Person *primaryOwner, const Person *jointOwner); virtual BankAccount(); virtual void makeDeposit(double amount) = 0; virtual void

2、makeWithdrawal(double amount) = 0; virtual double balance() const = 0; .;很多银行现在提供了多种令人眼花缭乱的帐户类型,但为简化起见,我们假设只有一种银行帐户,称为存款帐户:class SavingsAccount: public BankAccount public: SavingsAccount(const Person *primaryOwner, const Person *jointOwner); SavingsAccount(); void creditInterest(); / 给帐户增加利息 .;这远远称不

3、上是一个真正的存款帐户,但还是那句话,现在什么年代?至少,它满足我们现在的需要。银行想为它所有的帐户维持一个列表,这可能是通过标准库(参见条款49)中的list类模板实现的。假设列表被叫做allAccounts:list allAccounts; / 银行中所有帐户和所有的标准容器一样,list存储的是对象的拷贝,所以,为避免每个BankAccount存储多个拷贝,银行决定让allAccounts保存BankAccount的指针,而不是BankAccount本身。假设现在准备写一段代码来遍历所有的帐户,为每个帐户计算利息。你会这么写:/ 不能通过编译的循环(如果你以前从没/ 见过使用 迭代子

4、的代码,参见下文)for (list:iterator p = allAccounts.begin(); p != allAccounts.end(); +p) (*p)-creditInterest(); / 错误!但是,编译器很快就会让你认识到:allAccounts包含的指针指向的是BankAccount对象,而非SavingsAccount对象,所以每次循环,p指向的是一个BankAccount。这使得对creditInterest的调用无效,因为creditInterest只是为SavingsAccount对象声明的,而不是BankAccount。如果list:iterator p

5、= allAccounts.begin() 在你看来更象电话线中的噪音,而不是C+,那很显然,你以前无缘见识过C+标准库中的容器类模板。标准库中的这一部分通常被称为标准模板库(STL),你可以在条款49和M35初窥其概貌。但现在你只用知道,变量p工作起来就象一个指针,它将allAccounts中的元素从头到尾循环一遍。也就是说,p工作起来就好象它的类型是BankAccount*而列表中的元素都存储在一个数组中。上面的循环不能通过编译很令人泄气。的确,allAccounts是被定义为保存BankAccount*,但要知道,上面的循环中它事实上保存的是SavingsAccount*,因为Savin

6、gsAccount是仅有的可以被实例话的类。愚蠢的编译器!对我们来说这么显然的事情它竟然笨得一无所知。所以你决定告诉它:allAccounts真的包含的是SavingsAccount*:/ 可以通过编译的循环,但很糟糕for (list:iterator p = allAccounts.begin(); p != allAccounts.end(); +p) static_cast(*p)-creditInterest();一切问题迎刃而解!解决得很清晰,很漂亮,很简明,所做的仅仅是一个简单的转换而已。你知道allAccounts指针保存的是什么类型的指针,迟钝的编译器不知道,所以你通过一个转

7、换来告诉它,还有比这更合理的事吗?在此,我要拿圣经的故事做比喻。转换之于C+程序员,就象苹果之于夏娃。这种类型的转换 - 从一个基类指针到一个派生类指针 - 被称为 向下转换,因为它向下转换了继承的层次结构。在刚看到的例子中,向下转换碰巧可以工作;但正如下面即将看到的,它将给今后的维护人员带来恶梦。还是回到银行的话题上来。受到存款帐户业务大获成功的激励,银行决定再推出支票帐户业务。另外,假设支票帐户和存款帐户一样,也要负担利息:class CheckingAccount: public BankAccount public: void creditInterest(); / 给帐户增加利息 .

8、;不用说,allAccounts现在是一个包含存款和支票两种帐户指针的列表。于是,上面所写的计算利息的循环转瞬间有了大麻烦。第一个问题是,虽然新增了一个CheckingAccount,但即使不去修改循环代码,编译还是可以继续通过。因为编译器只是简单地听信于你所告诉它们(通过static_cast)的一切:*p指向的是SavingsAccount*。谁叫你是它的主人呢?这会给今后维护带来第一个恶梦。维护期第二个恶梦在于,你一定想去解决这个问题,所以你会写出这样的代码:for (list:iterator p = allAccounts.begin(); p != allAccounts.end(

9、); +p) if (*p 指向一个 SavingsAccount) static_cast(*p)-creditInterest(); else static_cast(*p)-creditInterest();任何时候发现自己写出 如果对象属于类型T1,做某事;但如果属于类型T2,做另外某事 之类的代码,就要扇自己一个耳光。这不是C+的做法。是的,在C,Pascal,甚至Smalltalk中,它是很合理的做法,但在C+中不是。在C+中,要使用虚函数。记得吗?对于一个虚函数,编译器可以根据所使用对象的类型来保证正确的函数调用。所以不要在代码中随处乱扔条件语句或开关语句;让编译器来为你效劳。如

10、下所示:class BankAccount . ; / 同上/ 一个新类,表示要支付利息的帐户class InterestBearingAccount: public BankAccount public: virtual void creditInterest() = 0; .;class SavingsAccount: public InterestBearingAccount . / 同上;class CheckingAccount: public InterestBearingAccount . / as above;用图形表示如下: BankAccount | InterestBea

11、ringAccount / / / CheckingAccount SavingsAccount因为存款和支票账户都要支付利息,所以很自然地想到把这一共同行为转移到一个公共的基类中。但是,如果假设不是所有的银行帐户都需要支付利息(以我的经验,这当然是个合理的假设),就不能把它转移到BankAccount类中。所以,要为BankAccount引入一个新的子类InterestBearingAccount,并使SavingsAccoun和CheckingAccount从它继承。存款和支票账户都要支付利息的事实是通过InterestBearingAccount的纯虚函数creditInterest来体

12、现的,它要在子类SavingsAccount和CheckingAccount中重新定义。有了新的类层次结构,就可以这样来重写循环代码:/ 好一些,但还不完美for (list:iterator p = allAccounts.begin(); p != allAccounts.end(); +p) static_cast(*p)-creditInterest();尽管这个循环还是包含一个讨厌的转换,但代码已经比过去健壮多了,因为即使又增加InterestBearingAccount新的子类到程序中,它还是可以继续工作。为了完全消除转换,就必须对设计做一些改变。一种方法是限制帐户列表的类型。如果

13、能得到一列InterestBearingAccount对象而不是BankAccount对象,那就太好了:/ 银行中所有要支付利息的帐户list allIBAccounts;/ 可以通过编译且现在将来都可以工作的循环for (list:iterator p = allIBAccounts.begin(); p != allIBAccounts.end(); +p) (*p)-creditInterest();如果不想用上面这种 采用更特定的列表 的方法,那就让creditInterest操作使用于所有的银行帐户,但对于不用支付利息的帐户来说,它只是一个空操作。这个方法可以这样来表示:class BankAccount public:

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 生活休闲 > 科普知识

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号