2009-04-22 11 views
5

Tengo este código para representar banco:
Manipular con punteros a objetos de clases derivadas a través de punteros a clase base objetos

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

BaseBankAccount es una clase abstracta para todas las cuentas en un banco:

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

El problema es que cuando manipulo punteros a objetos de clase derivados mediante punteros a objetos de clase base, el conjunto de métodos que puedo invocar está restringido por la interfaz pública BaseBankAccount, sin importar de qué tipo sea el objeto REAL.

Por ejemplo, no todas las cuentas tiene una opción para aumentar la cantidad ya invertida - es así, me incluyo dejase`t este método en una clase base:

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

entonces, yo quiero llamarlo:

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

Entonces, ¿qué puedo hacer para tener acceso a la interfaz de los objetos de clase derivados en este caso? Por lo que sé, downcasting a concreto tipo cada vez que necesito llamar a la funcionalidad específica no es bueno.
¿Crear una clase abstracta más derivada de esto para expandir la interfaz?
Crear una jerarquía paralela para cada tipo específico que necesito implementar (¿parece algo pesado que hacer?)

+0

métodos son virtuales, por supuesto - lo siento – chester89

+0

me parece que la forma más fácil de resolver esto es subir los campos de datos de BankAccount a su clase base – chester89

Respuesta

7

Una solución para el acceso a características de clase más derivados de una clase base es el patrón visitante.

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

La interacción no es inmediatamente obvia. Define un método virtual puro AcceptVisitor en su clase base, que toma un objeto de tipo IVisitor como parámetro. IVisitor tiene un Método por clase derivada en la jerarquía. Cada clase derivada implementa AcceptVisitor de manera diferente y llama al método correspondiente a su tipo concreto (AccountTypeA & AccountTypeB), y le pasa una referencia concreta al método. Implementa la funcionalidad que usa los tipos más derivados de su jerarquía en objetos derivados de IVisitor. Wikipedia: Visitor Pattern

+0

la solución es muy sólida, pero ¿qué ocurre con la situación cuando quiero implementar mote que 1 método de clase derivada que misimg de clase base? – chester89

+0

No estoy seguro de entender este comentario. ConcreteVisitor :: VisitAccountTypeA tiene acceso a todo AccountTypeA y puede hacer cualquier cosa con él, no solo llamar a un método. Por lo general, obtendrá más de un Visitante "concreto" de IVisitor y los llamará después de la Operación que realizan. – TheFogger

0

Tener que abatir, aunque siempre es arriesgado, a veces es inevitable. Por ejemplo, antes de que las plantillas se introdujeran en Java, tenías que bajar todo el tiempo cuando usabas colecciones estándar, ya que solo funcionaban con objetos.

Supongo que cuando está realizando un downcasting, lo está haciendo seguro con dynamic_cast. También estoy asumiendo que todo es virtual en tu clase base y que simplemente lo omitiste en este código.

Específicamente para su pregunta, no hay una sola respuesta correcta. En términos generales, cuando agrupamos un grupo de clases derivadas en una colección heterogénea, estamos utilizando la colección como un "cubo de bits" o intentando trabajar en el denominador común más bajo (la interfaz de la clase base).

Si la carcasa es la primera, entonces puede ser necesario bajar la pendiente, aunque feo. Si el caso es el último, entonces tiene que preguntarse por qué querría manejar subtipos específicos de manera diferente, y si podría cambiar la implementación de la clase base para invocar de alguna manera las cosas relevantes en la clase derivada (por ejemplo, a través de un método derivado).

En cualquier caso, es posible que desee reconsiderar si una colección heterogénea tiene sentido en primer lugar, en algunos casos realmente no lo hace. Por ejemplo, tal vez tenga sentido mantener colecciones por separado para cada tipo de cuenta principal, y tener un método "getAllAccounts" que devuelva el agregado.

1

La restricción a la interfaz pública de la clase base (y, por cierto, ¿no faltan algunas 'virtuales en lo que publicaste?) Es lo que hace C++. Si necesita acceder a funciones específicas que pertenecen solo a una clase derivada, entonces debe convertir el puntero a esa clase usando dynamic_cast.

Si encuentra la necesidad de utilizar dynamic_cast mucho, entonces su diseño es posiblemente deficiente, pero es muy difícil comentar sobre esto sin detallar exactamente los detalles del dominio comercial con el que está tratando.

Una posible forma de solucionar el problema es proporcionar métodos que acceden a los componentes de una cuenta. Por ejemplo, un método de clase base GetPortfolio() podría devolver un puntero de objeto Portafolio pero solo para clases de cuenta que tienen Portafolios. Para otras clases, defina su método GetPortfolio() como devolver NULL. Una vez que tiene el puntero de la cartera, trabaja en la interfaz de la cartera (que a su vez puede representar una jerarquía de clases) en lugar de la cuenta bancaria.

+0

Nunca he comprobado, pero lo haría esta compilación? (Tener el = 0 pero sin un virtual?) – Uri

+0

No, no debería, pero los escritores del compilador a menudo tratan = 0 como su propiedad especial y hacen cosas extrañas con él. –

+0

Hubiera pensado que esto sería parte de la gramática ... – Uri

1

¿Por qué necesita "acceso a la interfaz de objetos de clase derivados"?

Podría servir de ayuda si desea proporcionar un ejemplo de una subclase de BaseBankAccount con un método que desee llamar.

Supongo que los métodos puros en su clase BaseBankAccount están destinados a ser virtuales también?

Si desea llamar a métodos de subclases de BaseBankAccount, normalmente deberá agregar esos métodos (como virtuales) a la clase base BaseBankAccount. Si los métodos no tienen sentido para todas las subclases de BaseBankAccount, es posible que deba replantearse el diseño de su clase.

E.g.

class BaseBankAccount { 
    public: 
     BaseBankAccount() {} 

     virtual void someNewMethod() = 0; 

     // ... 
}; 

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

     // ... 
}; 
+0

tiene razón, tenían la intención de ser virtuales – chester89

Cuestiones relacionadas