`
kmplayer
  • 浏览: 508687 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

条款39:避免在继承体系中做向下转型(downcast)动作

 
阅读更多
1,先看个例子:
class Person { ... };
class BankAccount
{
public:
  BankAccount(const Person *primaryOwner,
              const Person *jointOwner);
  virtual ~BankAccount();
  virtual void makeDeposit(double amount) = 0;
  virtual void makeWithdrawal(double amount) = 0;
  virtual double balance() const = 0;
  ...
};

class SavingsAccount: public BankAccount
{
public:
  SavingsAccount(const Person *primaryOwner,
                 const Person *jointOwner);
  ~SavingsAccount();
  void creditInterest();                // add interest to account
  ...
};

使用:
list<BankAccount*> allAccounts;
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  (*p)->creditInterest();      // error!
}
错误:creditInterest在BankAccounts中不存在.

可行的纠正:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  static_cast<SavingsAccount*>(*p)->creditInterest(); //downcast
}

注:转型(cast)之于程序员,犹如苹果之于夏娃.

2,如果又有一个新的账户加入.
class CheckingAccount: public BankAccount
{
public:
  void creditInterest();    // add interest to account
  ...
};

我们这时候必须这么做:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  if (*p points to a SavingsAccount)
    static_cast<SavingsAccount*>(*p)->creditInterest();
  else
    static_cast<CheckingAccount*>(*p)->creditInterest();
}

根据型别做事,这不是C++的精神,应该使用虚拟函数.
class BankAccount { ... };      // as above

// new class representing accounts that bear interest
class InterestBearingAccount: public BankAccount
{
public:
  virtual void creditInterest() = 0; //提供接口
  ...
};
class SavingsAccount: public InterestBearingAccount
{
  ...                           // as above
};
class CheckingAccount: public InterestBearingAccount
{
  ...                           // as above
};

如下图所示:



这时候,你可以这么使用:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  static_cast<InterestBearingAccount*>(*p)->creditInterest();
}

3,还有另外一种解决的办法:
class BankAccount
{
public:
  virtual void creditInterest() {} //基类提供一个什么都不做的缺省实现
  ...
};
class SavingsAccount: public BankAccount { ... };
class CheckingAccount: public BankAccount { ... };

list<BankAccount*> allAccounts;// look ma, no cast!

for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  (*p)->creditInterest();
}

4,总结:为了摆脱downcast,不论花多少努力都是值得的.
但是偶尔还是无法摆脱downcast.
例如:当我们无权改变BankAccount, SavingsAccount, 或 allAccounts的定义.
这时候,相对于static_cast,还有个比较好的办法:"safe downcasting"(安全向下转型)
使用dynamic_cast,如果失败,会传回null指针.

回到一开始的地方:

class Person { ... };
class BankAccount
{
public:
  BankAccount(const Person *primaryOwner,
              const Person *jointOwner);
  virtual ~BankAccount();
  virtual void makeDeposit(double amount) = 0;
  virtual void makeWithdrawal(double amount) = 0;
  virtual double balance() const = 0;
  ...
};

class SavingsAccount: public BankAccount
{
public:
  void creditInterest();                // add interest to account
  ...
};

class CheckingAccount: public BankAccount
{
public:
  void creditInterest();                // add interest to account
  ...
};

使用:
list<BankAccount*> allAccounts; 

void error(const string& msg);     

//至少可以保证downcast失败时,可以侦测到.
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
  if (SavingsAccount *psa = dynamic_cast<SavingsAccount*>(*p))
  {
    psa->creditInterest();
  }
  else if (CheckingAccount *pca = dynamic_cast<CheckingAccount*>(*p))
  {
    pca->creditInterest();
  }
  // uh oh — unknown account type
  else
  {
    error("Unknown account type!");
  }
}

注:downcast必然导致if-then-else风格,比起虚函数,拙劣之极.
万不得已,不要这么使用.
  • 大小: 24.2 KB
分享到:
评论

相关推荐

    重构-改善既有代码的设计_简体中文

    - **目的**: 避免在代码中出现显式的向下转型,提高安全性。 - **步骤**: 创建一个新方法来处理向下转型;在该方法中检查类型,并返回转换后的对象。 14. **封装值域 (Encapsulate Field)** - **定义**: 将类中...

    downcast:用于下载播客的 Python 实用程序

    而`downcast`就是一款专为播客爱好者设计的Python实用程序,它可以帮助用户方便地下载播客节目,让用户在离线状态下也能享受丰富的音频内容。 `downcast`的核心功能在于其强大的播客抓取和下载能力。它利用Python的...

    downcast-localization:Downcast的本地化字符串

    例如,“AppName”键可能在英文版的.strings文件中对应“Downcast”,而在法文版中则对应“Downcast (version française)”。 三、.strings文件结构 在downcast-localization-master文件夹中,我们可以看到多语言...

    重构改善既有代码的设计中文版

    13. **Encapsulate Downcast(封装向下转型动作)** - 描述:封装向下转型的动作,提高代码的健壮性。 - 场景:当需要频繁进行类型转换时使用。 14. **Encapsulate Field(封装字段)** - 描述:将类的字段...

    重构-改善既有代码的设计(中文版)

    - **Encapsulate Downcast (封装“向下转型”动作):**减少类型转换错误的风险。 - **Encapsulate Field (封装字段):**保护类的内部状态不受外部干扰。 - **Extract Class (提取类):**从现有类中分离出新的类。 - ...

    介绍Boost libraries: C++编程的下一个突破

    它们通常用于编译时断言和类型安全的向下转型,这可以帮助程序员避免运行时错误。 4. Operators:在Boost的运算符库中,可以找到各种运算符的辅助函数。虽然C++标准库提供了一些运算符重载的机制,但Boost库在此...

    C++类库手册

    - **polymorphic_downcast**:用于向下转型,提供类型安全检查。 - **numeric_cast**:提供安全的数值类型转换。 - **lexical_cast**:实现字符串和其他基本类型的相互转换。 #### 四、工具类(Library 3: Utility...

    深度探索C++对象模型 超清版

    Type-Safe Downcast(保证安全的向下转型操作) Type-Safe Dynamic Cast(保证安全的动态转型) References并不是Pointers Typeid运算符 7.4 效率有了,弹性呢? 动态共享函数库(Dynamic Shared Libraries) 共享...

    重构-改善既有代码的设计

    #### 3.13 Encapsulate Downcast(封装“向下转型”动作) 通过创建一个安全的接口来执行向下转型操作,避免直接使用强制转换,从而提高代码的安全性。 #### 3.14 Encapsulate Field(封装值域) 将类中的属性封装...

    downcast:一个轻量级的 Markdown 网络发布平台

    Downcast的设计理念可能是为了提供一个快速、高效且易于维护的发布环境,避免复杂的后台编辑器和冗余的功能,让用户专注于内容的创作。使用Markdown的好处在于,其语法简洁,易于学习,同时还能保持源代码的整洁,...

Global site tag (gtag.js) - Google Analytics