Template Template è un buon modo per implementare DRY?

4

Ecco il mio problema:

Ho una struttura di classe come questa:

class Base
{
    private:
        SomeType    something;
        bool        isSomeValue;

    public:
        virtual void myMethod() = 0;
};

class Child1 : public Base
{
    virtual void myMethod() override
    {
        // do step 1
        for (int i = 0; i < something.size(); ++i)
        {
             // do step 2

             if (isSomeValue)
             {
                  // do step 3
             }
        }
        // do step 4
    }
};


class Child2 : public Base
{
    virtual void myMethod() override
    {
        // do step 1
        for (int i = something.size() - 1; i >= 0; --i)
        {
             // do step 2

             if (isSomeValue)
             {
                  // do step 3
             }
        }
        // do step 4
    }
};


class Child3 : public Base
{
    virtual void myMethod() override
    {
        // do step 1
        for (int i = 0; i < something.size(); ++i)
        {
             // do step 2

             if (isSomeValue)
             {
                  // do step 3
             }
             else
             {
                 break;
             }
        }
        // do step 4
    }
};

Come puoi vedere in Child2 e Child3 differiscono da Child1 come in Child2 il ciclo sta passando dalla direzione opposta e in Child3 c'è anche else con break istruzioni. Spero tu sia d'accordo sul fatto che questi algoritmi differiscono solo leggermente e faccio quasi copia del codice. Esiste un approccio migliore a strutture simili per non scrivere codice simile in molti luoghi diversi?

Ecco principio ASCIUTTO che voglio usare anche se non eseguo la copia del codice, solo scrivere simili codice. Ho appena visto un costrutto simile in diverse classi. Potrebbe Modello metodo modello utilizzato in modo intelligente per eliminare il codice simile? O qualsiasi altro approccio?

    
posta Narek 22.03.2016 - 14:39
fonte

4 risposte

5

Vorrei iniziare a ridefinire le parti comuni , non le diverse:

class Base
{
    protected:
        SomeType    something;
        bool        isSomeValue;

        void step1();
        void step2(int i);
        void step3IfSomeValue(int i);
        void step4();

    public:
        virtual void myMethod() = 0;
};

class Child1 : public Base
{
    virtual void myMethod() override
    {
        step1();
        for (int i = 0; i < something.size(); ++i)
        {
             step2(i);
             step3IfIsSomeValue(i);
        }
        step4();
    }
};


class Child2 : public Base
{
    virtual void myMethod() override
    {
        step1();
        for (int i = something.size() - 1; i >= 0; --i)
        {
             step2(i);
             step3IfIsSomeValue(i);
        }
        step4();
    }
};


class Child3 : public Base
{
    virtual void myMethod() override
    {
        step1();
        for (int i = 0; i < something.size(); ++i)
        {
             step2(i);
             step3IfIsSomeValue(i);
             if (!isSomeValue)
                 break;
        }
        step4();
    }
};

Naturalmente, in una seconda fase, con un certo sforzo, è possibile generalizzare questi tre metodi in un unico metodo, come indicato da @JoulinRouge (si noti che questo non mostra il modello del metodo del modello).

Tuttavia, devi considerare se questo migliora davvero il tuo codice. Chiediti: è questo qualcosa con il rischio reale di essere dimenticato di essere cambiato se devi aggiungere alcune funzionalità aggiuntive, perché hai "parti simili" in tre metodi, ma uno? Oppure è vero il contrario: diventa più difficile cambiare qualcosa perché i tre metodi (sopra) generalizzati in uno solo, e ora non puoi più modificarli individualmente? Se puoi rispondere a questa domanda, sai cosa fare.

    
risposta data 22.03.2016 - 16:57
fonte
3

Che ne dici di qualcosa di simile?

void myMethod(Boolean reverse, Boolean doElse)
{
// do step 1

int start = 0;
int stop = 0;
if(reverse)
{
    start = something.size();
    stop = 0;
}
foreach(i in range(start, stop))
{
     // do step 2

     if (isSomeValue)
     {
          // do step 3
     }
     else
     {
        if(doElse)
            break;
     }
}
    // do step 4
}
}

è meno leggibile ma non copi il codice copia-incolla

    
risposta data 22.03.2016 - 15:31
fonte
1

A volte ripetersi è una buona idea. O a volte non si sta effettivamente ripetendo.

Qui sembra che il flusso di controllo differisca, ma i diversi passaggi nei commenti sono gli stessi. In questo caso, la soluzione migliore è scrivere funzioni riutilizzabili che gestiscano i passaggi stessi e che possano essere richiamati ovunque siano necessari, quindi creare strutture di controllo di controllo diverse attorno a loro.

Potresti costruire oggetti o modelli elaborati per fare il flusso di controllo per te, ma uno deve chiedersi quale sarebbe il punto. Non è necessario che il flusso di controllo cambi durante l'esecuzione e non è necessario renderlo collegabile, quindi codificare il flusso di controllo in un modo chiaro ed esplicito e avere Step1() Step2() ecc. Per il punto in cui il codice è autenticamente lo stesso.

    
risposta data 22.03.2016 - 15:17
fonte
0

Un articolo che potresti trovare molto interessante è Herb Sutter Virtuality ( Idiota NVI )

Considerando le seguenti linee guida:

Guideline #1: Prefer to make interfaces nonvirtual, using Template Method design pattern.

È possibile utilizzare Metodo modello per rendere l'interfaccia stabile e non virtuale, delegando il lavoro personalizzabile a funzioni virtuali non pubbliche che sono responsabili dell'implementazione del comportamento personalizzabile. Dopo tutto, le funzioni virtuali sono progettate per consentire alle classi derivate di personalizzare il comportamento; è meglio non lasciare che le classi derivate pubblicamente personalizzino anche l'interfaccia ereditata, che dovrebbe essere coerente.

Guideline #2: Prefer to make virtual functions private.

Esistono funzioni virtuali per consentire la personalizzazione; a meno che non debbano essere invocati direttamente dal codice delle classi derivate, non è necessario renderli mai personali.

Potresti scrivere:

class Base
{
public:
  void myMethod()
  {
    // step1

    loop();

    // step4
  }

private:
  virtual void loop() = 0;

  void step2(int);
  void step3(int);

  SomeType    something;
  bool        isSomeValue;
};

class Child1 : public Base
{
  virtual void loop() override
  {
    for (int i = 0; i < something.size(); ++i)
    {
      step2(i);

      if (isSomeValue)
        step3(i);
    }
  }
};


class Child2 : public Base
{
  virtual void loop() override
  {
    for (int i = something.size() - 1; i >= 0; --i)
    {
      step2(i);

      if (isSomeValue)
        step3(i);
    }
  }
};

class Child3 : public Base
{
  virtual void loop() override
  {
    for (int i = 0; i < something.size(); ++i)
    {
      step2(i);

      if (isSomeValue)
        step3(i);
      else
        break;
    }
  }
};

Se si dovrebbe procedere ulteriormente dipende dalle considerazioni alla fine della risposta di Doc Brown.

    
risposta data 22.03.2016 - 18:21
fonte

Leggi altre domande sui tag