views:

1308

answers:

4

I have this code to represent bank:

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

BaseBankAccount is an abstract class for all accounts in a bank:

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() {}
};

The problem is, when I manipulate with pointers to derived class objects through pointers to base class objects, the set of methods I can call is restricted by BaseBankAccount public interface - no matter what type the REAL object is.

For example, not every account has an option to increase sum invested already - so, I didn`t include this method in a base class:

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() {}
};

then, I want to call it:

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

So, what can I do to get access to the interface of derived class objects in this case? As far as I know, downcasting to concrete type each time I need to call specific functionality is not good.
Create one more abstract class derived from this to expand the interface?
Create parallel hierarchy for each specific type I need to implement (looks a bit heavy thing to do)?

A: 

Having to downcast, while always risky, is sometimes unavoidable. For example, before templates were introduced into Java, you had to downcast all the time when using standard collections since they only worked with Objects.

I am assuming that when you are downcasting, you are making it safe with a dynamic_cast. I am also assuming that everything is virtual in your base class and you just omitted it in this code.

Specifically to your question, there isn't a single correct answer. Generally speaking, when you clump together a bunch of derived classes into a heterogeneous collection, you are either using the collection as a "bit bucket", or intending to work on the lowest common denominator (the interface of the base class).

If the case is the former, then downcasting, while ugly, may be necessary. If the case is the latter, then you have to ask yourself why you would want to handle specific subtypes differently, and whether you could change the implementation of the base class to somehow invoke the relevant stuff in the derived class (e.g., via a derived method).

In either case, you may want to reconsider whether a heterogenous collection makes sense in the first place, in some cases it really doesn't. For example, maybe it makes sense to separately maintain collections for each primary type of account, and have a "getAllAccounts" method that would return the aggregate?

Uri
+1  A: 

The restrictioon to base class public interface (and by the way, aren't there some 'virtual's missing in what you posted) is what C++ does. If you need to access specific functions that belong only to a derived class, then you need to cast the pointer to that class using dynamic_cast.

If you find the need to use dynamic_cast a lot, then your design is possibly wanting, but it's very difficult to comment on this without kniowing exact details of the business domain you are dealing with.

One possible way round the problem is to provide methods that access components of an account. For example, a base class method GetPortfolio() could returbn a Portfolio object pointer but only for account classes that have Portfolios. For other classes you define their GetPortfolio() method as returning NULL. Once you have the Portfolio pointer, you work on the portfolio interface (which itself may represent a class heirarchy) rather thean the BankAccount.

anon
I've never checked, but would this compile? (Having the =0 but without a virtual?)
Uri
No, it shouldn't, but compiler writers often treat =0 as their own special property and do strange things with it.
anon
I would have thought this would be part of the grammar...
Uri
It is - that doesn't stop compiler writers from messing with it.
anon
+1  A: 

Why do you need "access to the interface of derived class objects" ?

It might help for the purposes of discussion if you provide an example of a subclass of BaseBankAccount with a method you want to call.

I assume the pure methods in your BaseBankAccount class are meant to be virtual as well?

If you want to call methods of sub-classes of BaseBankAccount you would typically need to add those methods (as virtual) to the BaseBankAccount base class. If the methods doesn't make sense for all sub-classes of BaseBankAccount then you may need to rethink your class design.

E.g.

class BaseBankAccount {
    public:
        BaseBankAccount() {}

        virtual void someNewMethod () = 0;

        // ...
};

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

        // ...
};
jon hanson
you`re right - they meant to be virtual
chester89
+5  A: 

A solution to accessing more derived class features from a base class is the visitor pattern.

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
    }
};

The interaction is not immediately obvious. You define a pure virtual method AcceptVisitor in your base class, which takes an Object of type IVisitor as a parameter. IVisitor has one Method per derived class in the hierarchy. Each derived class implements AcceptVisitor differently and calls the method corresponding to its concrete type (AccountTypeA & AccountTypeB), and passes a concrete reference to itself to the method. You implement the functionality that uses the more derived types of your hierachy in objects deriving from IVisitor. Wikipedia: Visitor Pattern

TheFogger
the solution is very strong, but what about the situation when I want to implement mote than 1 method of derived class that base class missimg?
chester89
I'm not sure I understand this comment. ConcreteVisitor::VisitAccountTypeA has access to all of AccountTypeA and can do anything with it, not just call one method. Usually you'd derive more than one "Concrete"Visitor from IVisitor, and call them after the Operation they perform.
TheFogger