Работа с указателями на объекты производного класса через указатели на объекты базового класса - PullRequest
5 голосов
/ 22 апреля 2009

У меня есть этот код для представления банка:

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

Итак, что я могу сделать, чтобы получить доступ к интерфейсу объектов производного класса в этом случае? Насколько я знаю, понижение до конкретного типа каждый раз, когда мне нужно вызывать определенную функциональность , не годится .
Создать еще один абстрактный класс, полученный из этого, чтобы расширить интерфейс?
Создать параллельную иерархию для каждого конкретного типа, который мне нужно реализовать (выглядит немного сложным)?

Ответы [ 4 ]

7 голосов
/ 22 апреля 2009

Решением для доступа к более производным функциям класса из базового класса является шаблон посетителя.

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 в качестве параметра. IVisitor имеет один метод на производный класс в иерархии. Каждый производный класс по-разному реализует AcceptVisitor и вызывает метод, соответствующий его конкретному типу (AccountTypeA & AccountTypeB), и передает конкретную ссылку на сам метод. Вы реализуете функциональность, которая использует более производные типы иерархии в объектах, производных от IVisitor. Википедия: Шаблон посетителя

1 голос
/ 22 апреля 2009

Ограничение общедоступного интерфейса базового класса (и, кстати, разве нет того, чего не хватает в «виртуальном» в том, что вы опубликовали) - это то, что делает C ++. Если вам нужен доступ к определенным функциям, которые принадлежат только производному классу, вам нужно привести указатель на этот класс с помощью dynamic_cast.

Если вы обнаружите, что вам нужно часто использовать dynamic_cast, то ваш дизайн, возможно, вам не нужен, но очень сложно это прокомментировать, не зная точных деталей бизнес-сферы, с которой вы работаете.

Одним из возможных способов решения этой проблемы является предоставление методов доступа к компонентам учетной записи. Например, метод базового класса GetPortfolio () может восстановить указатель объекта Portfolio, но только для классов учетных записей, имеющих Portfolios. Для других классов вы определяете их метод GetPortfolio () как возвращающий NULL. Когда у вас есть указатель Портфолио, вы работаете с интерфейсом портфеля (который сам по себе может представлять иерархию классов), а не с BankAccount.

1 голос
/ 22 апреля 2009

Зачем вам нужен «доступ к интерфейсу объектов производного класса»?

Это может помочь в целях обсуждения, если вы предоставите пример подкласса BaseBankAccount с методом, который вы хотите вызвать.

Я предполагаю, что чистые методы в вашем классе BaseBankAccount также должны быть виртуальными?

Если вы хотите вызывать методы подклассов BaseBankAccount, вам обычно нужно добавить эти методы (как виртуальные) в базовый класс BaseBankAccount. Если методы не имеют смысла для всех подклассов BaseBankAccount, вам может потребоваться переосмыслить дизайн вашего класса.

1009 * Е.Г. *

class BaseBankAccount {
    public:
        BaseBankAccount() {}

        virtual void someNewMethod () = 0;

        // ...
};

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

        // ...
};
0 голосов
/ 22 апреля 2009

Необходимость опускания, хотя и всегда рискованная, иногда неизбежна. Например, до того, как шаблоны были введены в Java, вам приходилось все время принижать использование стандартных коллекций, поскольку они работали только с объектами.

Я предполагаю, что когда вы понижаетесь, вы делаете это безопасно с dynamic_cast. Я также предполагаю, что в вашем базовом классе все виртуально, и вы просто опустите его в этом коде.

В частности, на ваш вопрос нет единого правильного ответа. Вообще говоря, когда вы собираете кучу производных классов в гетерогенную коллекцию, вы либо используете коллекцию как «битовую корзину», либо намереваетесь работать с наименьшим общим знаменателем (интерфейсом базового класса).

Если это первый случай, то может быть необходимо уныние, хотя и некрасиво. Если это последний случай, то вы должны спросить себя, почему вы хотите по-разному обрабатывать определенные подтипы, и можете ли вы изменить реализацию базового класса, чтобы каким-либо образом вызывать соответствующие вещи в производном классе (например, через производный метод).

В любом случае вы можете пересмотреть вопрос о том, имеет ли гетерогенная коллекция смысл в первую очередь, а в некоторых случаях - нет. Например, может быть, имеет смысл отдельно поддерживать коллекции для каждого основного типа учетной записи и иметь метод «getAllAccounts», который будет возвращать совокупность?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...