Progetta due classi strettamente correlate

3

Ho i seguenti punti di progettazione di base:

  • Diciamo che ho bisogno di progettare 2 entità, ADecoder e BDecoder, quali sono quasi gli stessi, ad eccezione di alcuni dettagli.

  • Diciamo che entrambi hanno una funzione decodifica () che ovviamente sarebbe separati in quello che fanno, e ci sono anche altre funzioni, con la stessa caratteristica, in quanto eseguono operazioni completamente distinte basate sul tipo di decodificatore.

  • E, naturalmente, ci sono molte funzioni che hanno esattamente la stessa funzionalità, cioè sono agnostiche rispetto al tipo di decodificatore che è.

Ora dovrei progettare solo una classe e operare sulle sue istanze in base alle loro proprietà, decidere cosa fare nelle funzioni simili e lasciare che il codice per le funzioni di base sia lo stesso (switch / if else ecc.)?

O dovrei progettare una classe base astratta e implementare le funzioni comuni al suo interno e lasciare che le funzioni virtuali siano decise dalle rispettive classi figlie?

O c'è qualche altro metodo che sarebbe più adatto a questo tipo di problema?

    
posta Zoso 03.04.2018 - 07:28
fonte

4 risposte

4

Come spesso, dipende.

Se i due decoder decodificare lo stesso formato con lievi variazioni (esempio: differenti versioni dello stesso formato, o forse XML con o senza le entità carattere), allora dovrebbe essere un decoder con proprietà che selezionano tra le varianti

Se è due formati distinti, probabilmente non ci dovrebbero essere due decoder (ma non sempre, Clang ha lo stesso parser per C e C ++ perché i benefici di condivisione semplicemente superano gli svantaggi concettuali), e l'unica domanda è come si condivide il codice è simile.

Una semplice opzione è quella di estrarre elementi comuni in classi e funzioni di utilità. Ad esempio, se i due formati hanno lo stesso tipo di token, potresti voler utilizzare un tokenizer comune per questo.

Per parser ancora più simili, il modello di metodo template offre se stesso. (Si noti che il modello qui non significa modelli C ++.) Questo può essere implementato in C ++ tramite classi di base astratte o utilizzando il modello di modello curiosamente ricorrente (CRTP). Questo è principalmente un compromesso tra prestazioni e dimensioni del codice.

    
risposta data 03.04.2018 - 09:09
fonte
4

In primo luogo, vorrei creare un'interfaccia che descriva cosa fanno le classi:

interface IDecoder
{
    void Decode();
}

Successivamente, le tue classi decoder dovrebbero implementare quell'interfaccia in base alle loro specifiche.

Quando si tratta di metodi comuni, ci sono due approcci. O creare una classe base che implementa quei metodi comuni e ereditarla dalle classi decoder (accoppiamento verticale), o creare una classe separata che incapsula quella logica comune e inserirla nelle classi decoder (accoppiamento orizzontale).

Personalmente, preferisco la composizione all'eredità. Il motivo è che puoi testare più facilmente il codice e separare le responsabilità in questo modo.

    
risposta data 03.04.2018 - 10:05
fonte
2

In generale, sarei d'accordo con la risposta di @Vladimir Stokic. Dovresti creare un'interfaccia che descriva le responsabilità della decodifica. Che questa interfaccia sia in realtà un'interfaccia o una classe astratta non ha molta importanza purché i metodi pubblici (e le proprietà) siano gli stessi.

Quando si tratta di implementare la funzionalità comune, dovresti probabilmente considerare se quella funzionalità comune è parte della decodifica o che è una responsabilità diversa. In quest'ultimo caso, dovrebbe essere delegato a un'altra classe, consentendo test più semplici, il riutilizzo e, se necessario, la sostituzione. Se i metodi comuni sono solo implementazioni diverse dello stesso passo nella decodifica, allora un modello di metodo Template (GoF, come menzionato da @Sebastian Redl) sarebbe più appropriato.

Il metodo Template potrebbe apparire come questo (esempio C #):

public abstract class Decoder
{
    public void Decode()    // probably another return value and arguments'
    {
        DoStep1();
        DoStep2();
        DoStep3();
    }

    public abstract DoStep1();
    public abstract DoStep2();
    public abstract DoStep3();
}

public class ADecoder: Decoder
{
    public override DoStep1()
    {
        // implementation
    }
    public override DoStep2()
    {
        // implementation
    }
    public override DoStep3()
    {
        // implementation
    }
}

In questo modo è anche facile riconoscere le responsabilità che devono essere spostate in una classe separata: se uno dei tuoi passaggi non è specifico per la decodifica, devi rifattorizzarlo su un'altra classe.

    
risposta data 03.04.2018 - 11:47
fonte
1

Ad un certo punto vorrei assolutamente andare con l'astrazione. Il tuo meccanismo di decodifica non deve necessariamente sapere (ad un certo punto non dovrebbe sapere) chi esegue la decodifica. Il modulo proprietario / classe / metodo conosce solo quando deve essere eseguita la decodifica.

void doSomeDecoding(BaseDecoder* decoder){
    ...// do some ops before
    AbstractResult res = decoder->decode();
    ...// do some other stuff with the abstract result.
}

Se il tuo tipo di decodifica è in qualche modo uguale su tutte le classi figlie BaseDecoder che potresti voler usare con Generics (template in termini C ++). Questo è particolarmente utile quando l'istanza del risultato è importante.

template<class DecoderType,class ResultType>
ResultType decode(DecoderType a){
   return a.decode();
}

void doDecodingA(ADecoder a){
   AResult result = decode<ADecoder,AResult>(a);
}

void doDecodingB(BDecoder b){
   BResult result = decode<BDecoder,BResult>(b);
}
    
risposta data 03.04.2018 - 09:25
fonte

Leggi altre domande sui tag