【问题标题】:Manipulating with pointers to derived class objects through pointers to base class objects通过指向基类对象的指针使用指向派生类对象的指针进行操作
【发布时间】:2009-04-22 06:59:32
【问题描述】:

我有这个代码来代表银行:

class Bank {
    friend class InvestmentMethod;
    std::vector<BaseBankAccount*> accounts;
public:
//...

BaseBankAccount 是银行中所有账户的抽象类:

class BaseBankAccount {
    public:
        BaseBankAccount() {}
        virtual int getInterest() const = 0;
        virtual int getInvestedSum() const = 0;
        virtual void increaseDepositSum(int additionalSum) = 0;
        virtual void close(std::string& reason) = 0;
        virtual ~BaseBankAccount() {}
};

问题是,当我通过指向基类对象的指针使用指向派生类对象的指针进行操作时,我可以调用的方法集受到 BaseBankAccount 公共接口的限制——无论 REAL 对象是什么类型。

例如,并非每个账户都可以选择增加已投资金额 - 所以,我没有在基类中包含此方法:

class BankAccount: public BaseBankAccount {
protected:
    BaseDeposit* deposit;
    double sumInvested;
public:
    BankAccount(int sum, int term, int inter): sumInvested(sum), depositTerm(term), interest(inter) {}
    int getInterest() const { return interest; }
    int getInvestedSum() const { return sumInvested; }
    void prolong(int increaseTerm) {
        depositTerm += increaseTerm;
    }
    void increaseInvestment(double addition) {
            sumInvested += addition;
    }
    virtual ~BankAccount() {}
};

那么,我想叫它:

Bank bank1(...);
bank1.accounts[i]->increaseInvestment(1000.0);

那么,在这种情况下,我该怎么做才能访问派生类对象的接口呢?据我所知,每次我需要调用特定功能时向下转换为具体类型不好
再创建一个派生自此的抽象类以扩展接口?
为我需要实现的每个特定类型创建并行层次结构(看起来有点繁重)?

【问题讨论】:

  • 方法当然是虚拟的 - 对不起
  • 在我看来,解决这个问题的最简单方法是将数据字段从 BankAccount 推送到其基类

标签: c++ inheritance pointers


【解决方案1】:

从基类访问更多派生类功能的解决方案是访问者模式。

class BaseBankAccount {
public:
    ...
    virtual void AcceptVisitor(IVisitor& v) = 0;
};


class AccountTypeA : public BaseBankAccount {
public:
   void TypeAFeature() {...}

   void AcceptVisitor(IVisitor& v)
   {
       v.VisitAccountTypeA(*this);
   }
};

class AccountTypeB : public BaseBankAccount {
public:
   void TypeBFeature() {...}

   void AcceptVisitor(IVisitor& v)
   {
       v.VisitAccountTypeB(*this);
   }
};

class IVisitor {
public:
    virtual void VisitAccountTypeA(AccountTypeA& account) = 0;
    virtual void VisitAccountTypeB(AccountTypeB& account) = 0;
};

class ConcreteVisitor : public IVisitor{
public:
    void VisitAccountTypeA(AccountTypeA& account) 
    {
         account.TypeAFeature(); //Can call TypeA features
    }

    void VisitAccountTypeB(AccountTypeB& account) 
    {
         account.TypeBFeature(); //Can call TypeB Features
    }
};

交互作用不是很明显。你在你的基类中定义了一个纯虚方法 AcceptVisitor,它接受一个 IVisitor 类型的 Object 作为参数。 IVisitor 在层次结构中的每个派生类都有一个方法。每个派生类都以不同的方式实现 AcceptVisitor,并调用与其具体类型(AccountTypeA 和 AccountTypeB)对应的方法,并将对自身的具体引用传递给该方法。您实现的功能在派生自 IVisitor 的对象中使用更多派生类型的层次结构。维基百科:Visitor Pattern

【讨论】:

  • 解决方案很强大,但是当我想实现基类missimg的派生类的mote than 1方法时呢?
  • 我不确定我是否理解此评论。 ConcreteVisitor::VisitAccountTypeA 可以访问所有 AccountTypeA 并且可以用它做任何事情,而不仅仅是调用一个方法。通常你会从 IVisitor 派生多个“具体”Visitor,并在它们执行的操作之后调用它们。
【解决方案2】:

为什么需要“访问派生类对象的接口”?

如果您提供 BaseBankAccount 子类的示例以及您要调用的方法,这可能有助于讨论。

我假设 BaseBankAccount 类中的纯方法也应该是虚拟的?

如果您想调用 BaseBankAccount 子类的方法,您通常需要将这些方法(作为虚拟方法)添加到 BaseBankAccount 基类中。如果这些方法对 BaseBankAccount 的所有子类都没有意义,那么您可能需要重新考虑您的类设计。

例如

class BaseBankAccount {
    public:
        BaseBankAccount() {}

        virtual void someNewMethod () = 0;

        // ...
};

class SavingsBankAccount : public BaseBankAccount {
    public:
        virtual void someNewMethod () {
            // ...
        }

        // ...
};

【讨论】:

    【解决方案3】:

    对基类公共接口的限制(顺便说一句,您发布的内容中是否缺少一些“虚拟”)是 C++ 所做的。如果您需要访问仅属于派生类的特定函数,则需要使用 dynamic_cast 将指针强制转换为该类。

    如果您发现需要大量使用 dynamic_cast,那么您的设计可能需要,但如果不了解您正在处理的业务领域的确切细节,很难对此发表评论。

    解决此问题的一种可能方法是提供访问帐户组件的方法。例如,基类方法 GetPortfolio() 可以返回投资组合对象指针,但仅适用于具有投资组合的账户类。对于其他类,您将其 GetPortfolio() 方法定义为返回 NULL。一旦有了 Portfolio 指针,您就可以处理投资组合接口(它本身可能代表一个类层次结构)而不是 BankAccount。

    【讨论】:

    • 我从未检查过,但这会编译吗? (有 =0 但没有虚拟?)
    • 不,不应该,但编译器编写者经常将 =0 视为他们自己的特殊属性并用它做一些奇怪的事情。
    • 它是 - 这并不能阻止编译器编写者弄乱它。
    【解决方案4】:

    不得不沮丧,虽然总是有风险,但有时是不可避免的。 例如,在将模板引入 Java 之前,您必须在使用标准集合时一直向下转换,因为它们只适用于对象。

    我假设当您向下转换时,您使用 dynamic_cast 使其安全。我还假设您的基类中的所有内容都是虚拟的,而您只是在此代码中省略了它。

    具体到您的问题,没有一个正确答案。一般来说,当你将一堆派生类聚集到一个异构集合中时,你要么将集合用作“比特桶”,要么打算在最低公分母(基类的接口)上工作。

    如果情况是前者,那么向下转型,虽然丑陋,但可能是必要的。 如果是后者,那么您必须问自己为什么要以不同方式处理特定的子类型,以及是否可以更改基类的实现以某种方式调用派生类中的相关内容(例如,通过派生类方法)。

    在任何一种情况下,您可能首先要重新考虑异构集合是否有意义,在某些情况下确实没有意义。例如,为每种主要类型的帐户分别维护集合,并使用“getAllAccounts”方法返回聚合数据可能有意义吗?

    【讨论】:

      猜你喜欢
      • 2014-06-16
      • 2021-05-20
      • 2012-02-05
      • 1970-01-01
      • 2021-07-02
      • 1970-01-01
      • 2023-02-09
      • 2011-11-04
      • 1970-01-01
      相关资源
      最近更新 更多