E 'possibile refactoring ereditarietà di composizione quando i metodi virtuali sono chiamati all'interno della classe base?

5

Diciamo che ho una classe chiamata Country e due sottoclassi chiamate AI e Player. La classe Paese ha un numero di metodi virtuali per consentire comportamenti specifici del giocatore o specifici dell'IA.

Voglio che il giocatore possa cambiare nazione durante una partita. A tal fine, sto rifattando la relazione ereditaria con la composizione. Quindi potrò cambiare l'istanza interna del Paese di un giocatore per un altro in qualsiasi momento.

Questo solleva un po 'un problema: quando un oggetto esterno tenta di chiamare uno di questi metodi virtuali su un Player, viene delegato correttamente al metodo Paese dopo che è stata elaborata qualsiasi elaborazione appropriata fatto. Ma quando un metodo interno chiama uno dei metodi virtuali, il codice nella versione Player o AI non ha più possibilità di essere eseguito.

Quindi ora mi rimane l'aggiornamento di ognuno di questi metodi per assicurarmi che entrambe le versioni siano chiamate, il che di per sé non è un compito banale (loop infiniti, chiunque?).

Questo sembra molto più difficile di quanto dovrebbe essere. Ho già fatto dei refactoring simili e non ho mai avuto questo problema. Perché? Mi manca qualche osservazione apparentemente ovvia?

Modifica per un esempio concreto ora che ho di nuovo accesso al codice:

class Country
{
public:
    virtual void AddMoney(float money)
    {
        _vMoney += money;
    }
    void TakeLoan()
    {
        float loan = ...;
        AddMoney(loan); // <-- does not route through AI or Player implementation
    }
}

class AI  // : public Country
{
private:
    Country *pCountry;

public:
    virtual void AddMoney(float x)
    {
        pCountry->AddMoney(x); // formerly Country::AddMoney(x)
        AllocateMoney(x);
    }
}
    
posta mmyers 06.06.2011 - 19:41
fonte

2 risposte

5

Quindi facciamo questo concreto-ish.

class Country {
  virtual void foo();
  void bar() {
    foo();
  }
}

Verso:

class Country {
  private thingWhichWasSubclass;
  void bar() {
    thingWhichWasSubclass.foo();
  }
}

È giusto? Che ne dici di una fase intermedia:

class Country {
  private thingWhichWasSubclass;
  void foo() {
    thingWhichWasSubclass.foo();
  }
  void bar() {
    foo();
  }
}

Lo farebbe? Quindi (ovviamente) inline Country.foo ().

    
risposta data 06.06.2011 - 19:52
fonte
1

Avrei ottenuto quasi la stessa domanda, ma questa volta in Java. Ho trovato una soluzione che utilizza classi nidificate non statiche in java, che in C ++ si tradurrebbe in qualcosa di simile:

class AI  // : public Country
{
private:
    Country *pCountry; 

public:
    class Super : public Country {
        AI* outerThis;

    public:
        Super(AI* outer, ...)
        {
            outerThis = outer;
        }


        virtual void AddMoney(float x)
        {
            Country::AddMoney(x);
            outerThis->AllocateMoney(x);
        }
    } 

    // Just delegating calls to pCountry...
    virtual void AddMoney(float x)
    {
        pCountry->AddMoney(x);
    }
}

Quindi ovviamente devi istanziare AI :: Super per ottenere un Paese associato a un oggetto IA specifico. Forse lo istanzia dall'oggetto IA stesso (che elimina la necessità di AI: Super di essere pubblico).

Sostanzialmente sostituire l'ereditarietà con la composizione sta facendo esplicitamente ciò che viene fatto dietro la scena. Dietro la scena è la composizione più l'estensione della tabella di invio delle funzioni virtuali. In assenza di funzioni virtuali è semplice, ma nel caso delle funzioni virtuali dovrai tenerne conto. Il modo normale di fare questo (dietro le quinte) è di avere la tabella virtuale costituita da voci di classe base (con funzione sostituita sostituita) seguita dalle voci della classe derivata, ma potrebbe anche essere implementata avendo vtables separati per l'oggetto base (con funzioni sostituite sostituite) e una per i nuovi metodi virtuali dalla classe derivata (come nel mio esempio). In quest'ultimo caso si potrebbe assegnare l'oggetto base separatamente e memorizzare solo il puntatore ad esso.

L'unico riferimento circolare lei è l'IA < - > AI :: Super che potrebbe comportare chiamate circolari. Tuttavia, le uniche chiamate da AI ad AI :: Super dovrebbero essere quelle di avvolgere i metodi dalla classe base, e le chiamate da AI :: Super ad AI sono analoghe ai metodi di chiamata nella classe Derived, non dovrebbero verificarsi ricorsioni infinite.

    
risposta data 08.05.2015 - 11:09
fonte

Leggi altre domande sui tag