Inizializzazione delle classi derivate allo stesso modo

3

Ho una Base di classe che ha diversi figli, ad esempio A, B, C. Per scopi di test mi piacerebbe prendere in giro quelle classi derivate derivandole da loro. Quindi MockA deriva da A, MockB deriva da B e così via.

Il problema è che MockA, MockB, ... ereditano tutti i membri protetti da Base che devono essere impostati nello stesso modo in ogni classe di simulazione. Il codice ha il seguente aspetto:

MockA::init()
{
    x = 1;     // inherited from Base
    y = "abc"; // inherited from Base
    z = 0.5;   // inherited from Base
}

MockB::init()
{
    x = 1;     // inherited from Base
    y = "abc"; // inherited from Base
    z = 0.5;   // inherited from Base
}

e così via.

Quindi la mia domanda è: come posso evitare l'inizializzazione di questo taglio e incolla? Posso ottenerlo senza toccare Base, A, B, C, ...?

    
posta TobiMcNamobi 16.05.2013 - 11:04
fonte

4 risposte

5

Vuoi un "mixin". In C ++ vengono solitamente implementati con modelli e in particolare utilizzando CRTP, il "modello di modello curiosamente ricorrente" . Potrebbe essere eccessivo se si tratta solo di un codice comune, che la risposta di Pierre sembra più appropriata. Ma man mano che cresce la quantità di codice comune per le classi di simulazione, anche il valore del modello.

CRTP di base sarebbe come questo:

template <typename ConcreteT> class Mock {
    initMock() {
        static_cast<ConcreteT *>(this)->x = 1;
        static_cast<ConcreteT *>(this)->y = "abc";
        static_cast<ConcreteT *>(this)->z = 0.5;
    }
};

class MockA : A, Mock<MockA> { ... };
//                    ^^^^^ specialized on the type that inherits it, that's the point

Qui renderebbe semplice ereditare A attraverso la classe Mock, quindi

template <typename BaseT> class Mock : public BaseT {
    initMock() {
        x = 1;
        y = "abc";
        z = 0.5;
    }
};

class MockA : Mock<A> { ... };
//                 ^ just the base class

Questo elimina i brutti static_casts, ma non ti consente di fornire metodi in MockA che verranno chiamati da Mock . Ma puoi avere entrambi ...

template<typename ConcreteT, typename BaseT>
class Mock : public BaseT { ... };

class MockA : Mock<MockA, A> { ... };

Ora Mock può chiamare i metodi di MockA su se stesso tramite il cast statico e i metodi call di A (o meglio la base finale) direttamente.

Nota: i template di mixin sono fondamentalmente la stessa cosa di mixin in Ruby o interfacce con implementazioni di metodi introdotte in Java 8. Questi linguaggi sono un costrutto speciale, perché non hanno nulla della potenza dei template C ++, ma l'implementazione è simile: una classe intermedia è costruita dal compilatore e inserita nella gerarchia.

    
risposta data 16.05.2013 - 13:12
fonte
1

Bene, puoi introdurre un metodo statico che restituisce un insieme predefinito di tutti i valori dei membri protetti e recuperarli in ogni metodo init nei campi degli oggetti mock.

Modifica:

MockA::init()
{
  initMock(MockInitializer.getDefaultValues());
  //go on with your code
}

MockA::initMock(anystruct def_values)
{
  //assign your values
}

Il problema è che in qualsiasi posto dovrai svolgere il compito, perché non farlo nel suo stesso metodo?

    
risposta data 16.05.2013 - 11:10
fonte
1

Ecco come vorrei farlo se capisco il tuo problema:


class Base
{
protected:
    initMock()
    {
        x = 1;
        y = "abc";
        z = 0.5;
    }
};

class A { /* Whatever you want ... */ };

class B { /* Whatever you want ... */ };

class MockA : public A, public Base
{
public:
    initMock()
    {
         Base::initMock();
    }

};

E puoi fare la stessa cosa con MockB.

    
risposta data 16.05.2013 - 11:39
fonte
0

Se Base è la classe base comune di A, B, C che detiene membri protetti Base::x , Base::y , ecc., quindi suppongo che ci siano metodi di accesso come

Base::SetX(type_x xval){x=_val;}

ecc. Quindi scrivi una funzione statica in una classe helper MockInit

void MockInit::initForTests(Base &b)
{
    b.SetX(1);
    b.SetY("abc");
    b.SetZ(0.5);
}

e chiama questa funzione in questo modo:

MockA::init()
{
     MockInit::initForTests(*this);
}

MockB::init()
{
     MockInit::initForTests(*this);
}

Questo è tutto - semplice e stupido. Tuttavia, se non esistono tali metodi di accesso e non si ha assolutamente accesso a x, y, z dall'esterno della gerarchia dell'ereditarietà, prenderei in considerazione la possibilità di modificare il progetto iniziale di Base per una migliore testabilità. Almeno, vorrei prendere in considerazione MockInit una classe di amici di Base .

    
risposta data 16.05.2013 - 14:17
fonte

Leggi altre domande sui tag