che deriva, classi decoratore derivate. Come? Suona come una buona idea o ci sono modi più adatti?

3

Seguo questo esempio sui pattern di decoratore per implementare oggetti con la quale la funzionalità può essere accoppiata dinamicamente. Vale a dire:

class I: is the interface class, common to both "core" class and "decorator base" class
class A: is the "core functionality class"
class D: is the "decorator base" class
classes X,Y,Z: inherit from the "decorator base" class and extend functionality of the "core class" dynamically.

Normalmente creare un'istanza di un oggetto con tutte le funzionalità sarà simile a:

I * anYZ = new Z( new Y( new A ));
I * anXY = new Y( new X( new A ));
I * anXYZ = new Z( new Y( new X( new A )));

o

Z * anYZ = new Z( new Y( new A ));
Y * anXY = new Y( new X( new A ));
Z * anXYZ = new Z( new Y( new X( new A )));

Quest'ultimo gruppo è particolarmente utile se voglio usare una funzione come Z::doSmth() , che non è definita nella classe "I".

Questo ha funzionato fino ad ora, perché tutte le funzionalità comuni sono state inserite nella classe "A" e la maggior parte delle funzionalità non comuni è stata classificata nelle classi XYZ. Inoltre (ed è per questo che ho usato il pattern decoratore) all'istanziazione selezionerò le coppie o terzine della funzionalità che voglio.

Concentrandosi sulle classi di decoratori derivate "XYZ", diciamo che ho un metodo virtuale puro in ciascuna di esse:

struct X /*and class Y  and class Z*/ : public D {
    virtual void doSmthElse() = 0;
};

Un metodo doSmthElse() diverso dovrebbe essere eseguito per diverse istanze di "anXY", "anXYZ", ecc.

Qual è il modo corretto per farlo?

Una% derivata di% di conversione funzionerebbe? Sembra che questo metodo "distrugga" l'intero concetto del modello di decoratore. Sembra anche che il problema del "diamante" emergerebbe in questo modo, e mentre "l'ereditarietà virtuale" lo risolverebbe, dovrei comunque chiarire al compilatore quale delle stesse funzioni chiamate delle classi "XYZ" dovrebbero essere chiamate quando si accede da un oggetto di tipo "K". Un'istantanea sembrerebbe quindi

K * anK = new K( new A );

In alternativa, potrei derivare ciascuno di "XYZ" in questo modo:

struct KX : public X {
  void doSmthElse(){} //one implementation of doSmthElse
};

struct LX : public X {
  void doSmthElse(){} //another implementation of doSmthElse
};

struct KY : public Y {
  void doSmthElse(){} //yet another implementation of doSmthElse
};

Quindi posso nuovamente selezionare la funzionalità al momento dell'istanziamento in questo modo

Z * anKxYZ = new Z( new Y( new KX( new A )));
Z * anLxYZ = new Z( new Y( new LX( new A )));
Z * anXKyZ = new Z( new KY( new X( new A )));

Questo è più semplice ed evita i mal di testa del metodo precedente, tuttavia, ancora una volta mi sembra di ricreare diverse nuove classi.

Non sono stato in grado di farlo su Google perché non so se questo "problema" ha un nome e forse uno schema di codifica che lo risolve. Se questo è un problema ben noto e puoi indicarmi una direzione per leggere qualcosa, o puoi pensare ad una soluzione diversa, ne sarei lieto di sapere.

    
posta nass 03.09.2016 - 19:51
fonte

2 risposte

2

Ho la netta sensazione che il tuo "motivo decoratore" non corrisponda all'utilizzo convenzionale di quel termine.

Di solito, l'uso di questo modello implica una funzionalità extra nei metodi forniti dall'interfaccia , ad es. logging ("metodo X è stato chiamato con parametri Y e Z") o controllo dei limiti (il metodo su A si aspetta che param X sia compreso tra i valori Y e Z, decoratore lo verifica).

Tenendo presente questo, non è necessario un decorator base class - una classe decoratore implementa semplicemente l'interfaccia (inoltre, i decoratori di solito non pubblicano i propri metodi, poiché dovrebbero essere quasi sempre visti come un'istanza dell'applicazione interfaccia, nel tuo caso, di tipo I* o I& ).

Se ho capito bene il tuo problema, vuoi introdurre nuove funzionalità usando nuovi metodi (sui tuoi wrapper, dal momento che, per quanto posso dire, non sono decoratori), ma non so esattamente come fare tutti quelli aggiunti metodi visibili nell'oggetto risultante. In tal caso, suggerirei di utilizzare un approccio diverso: la composizione. (Puoi anche dare un'occhiata al modello di strategia .)

Esempio:

class A : public I {
    X* myX;
    Y* myY;
    Z* myZ;
public:
    A() { /* ... */ }
    void doSomething() { /* ... */ }

    X* getX() { return myX; }
    void setX(X* newX) {
        if(myX != nullptr) delete myX;
        myX = newX;
    }
    // repeat for Y and Z
};

// similar for Y and Z
class X {
public:
    // i left out constructor and stuff
    void doSomethingX() {
        // implement X specific methods
    }
};

Utilizzo:

void useSomethingofX(A* aObject) {
    if(aObject->getX() != nullptr) {
        // use X functionality
        aObject->getX()->doSomethingX();
    } else {
        // fallback or throw or ignore (X functionality not present
    }
}

void useSomethingOfXandYandZ(A* aObject) {
    if(aObject->getx() == nullptr || aObject->getY() == nullptr
        || aObject->getZ() == nullptr) return;

    aObject->getX()->doSomethingX();
    aObject->getY()->doSomethingY();
    aObject->getZ()->doSomethingZ();
}
    
risposta data 04.09.2016 - 19:15
fonte
2

Per quanto posso dire, questo probabilmente non è un problema ben noto. Ho giocato molto con le classi, le interfacce e i decoratori, eppure non ho mai incontrato un problema simile. Ciò che questo significa probabilmente è che ho evitato di mettermi nella posizione di avere un problema del genere, impiegando altre ben note best practice di refactoring e simlificazione sui miei progetti, quindi questo problema non ha mai avuto la possibilità di presentarsi.

Mi sembra che se devi fare cose così esotiche con i tuoi decoratori, forse la tua interfaccia originale "I" è troppo grande, troppo ricca. Non c'è vergogna nell'estrarre parte della funzionalità di "I" in un'interfaccia separata (ad esempio "J") e un metodo come J* getJ() in "I". In questo modo, le diverse preoccupazioni sono divise in diverse interfacce, ogni interfaccia ha i suoi decoratori e quindi la delega inizia a diventare una proposizione praticabile: quando viene richiamato getJ() , puoi ovviamente restituire this , ma puoi anche restituire myJmember che implementa J da solo, senza che nessuno debba derivarne.

    
risposta data 03.09.2016 - 20:40
fonte

Leggi altre domande sui tag