Design pattern per troppi parametri ctor all'interno della gerarchia di classi

7

Esiste un modello di progettazione per la gestione delle situazioni in cui i parametri dei costruttori di gerarchia di classi impongono che le classi più profonde abbiano troppi parametri?

Idealmente questo sarebbe in C ++, se la lingua è importante. Prendi in considerazione il seguente esempio generico:

class dependency1;
class dependency2;
class dependency3;

class ClassA
{
public:

    ClassA(dependency1* dep1) { }       
};

class ClassB : public ClassA
{
public:

    ClassB(dependency1* dep1, dependency2* dep2) 
      : ClassA(dep1){ }

};

class ClassC : public ClassB
{
public:

    ClassC(dependency1* dep1, dependency2* dep2, dependency3* dep3) 
      : ClassB(dep1, dep2) { }  
};

Ciascuna classe nella gerarchia aggiunge la sua dipendenza specifica, portando infine a 3 parametri nel ctor dell'ultima classe, anche se i 2 parametri vengono utilizzati solo per passare alle classi base. Puoi immaginare un esempio più complicato con 11 parametri simili nell'ultima classe ...

Capisco che potrebbe essere difficile rispondere a questa domanda in generale, ma mi piacerebbe sapere come affrontare questo problema in generale.

L'unica cosa vicina a ciò che ho trovato è usare il pattern Builder per la situazione con troppi parametri, ma non sembra adattarsi a questo caso (o semplicemente non vedo come aiuta a semplificare la gerarchia delle classi).

Sentiti libero di reindirmi per duplicare la risposta, non sono stato in grado di trovarne, ma sembra qualcosa che dovrebbe essere già stato chiesto.

    
posta Sil 26.03.2016 - 15:33
fonte

2 risposte

2

La mia opinione è che se hai una classe che direttamente dipende da 11 cose, probabilmente sta facendo troppo.

Consideriamo ora cosa succede se invece di una sottoclasse, ClassC aveva un membro di tipo ClassB che aveva un membro di tipo ClassA . Sembra che abbiamo ancora lo stesso problema, tranne che c'è una differenza questa volta. ClassC non dovrebbe essere direttamente dipendente da ClassB ; invece, dovrebbe contenere (un'interfaccia intorno) ClassB come parametro. Ora ClassC non ha più bisogno di prendere le dipendenze di ClassB come parametri aggiuntivi. Allo stesso modo per ClassB e ClassA . A questo punto, ogni classe prende solo le dipendenze di cui ha bisogno direttamente. Questo ha il vantaggio di indicare quanto sia accoppiata e complessa la classe.

Se si utilizza un contenitore di dipendenze per l'iniezione o il cablaggio manuale delle classi, il risultato finale è che ciascuna classe è autonoma. Ad esempio, ogni classe può essere compilata senza che le altre classi siano ancora esistenti (se sono state utilizzate le interfacce [classi di base astratte]). Non ci sono forti dipendenze. Costruire il grafico della dipendenza reale viene eseguito da una procedura di livello superiore; automaticamente da un contenitore per le dipendenze o manualmente.

Ritornando alla sottoclasse, non dovresti imbatterti nel problema che hai descritto a meno che tu non abbia progettato male il tuo codice. Come ho suggerito sopra, se stai iniziando a incappare in questo codice, puoi prendere l'indicatore che stai facendo qualcosa di sbagliato e dovrebbe essere un refactoring. Se ClassC è una sottoclasse sette livelli profondi o una classe di livello superiore è irrilevante. Alla fine della giornata, ClassC richiede tutte quelle dipendenze che dovresti prendere come segnale di avvertimento. Personalmente, raccomanderei di evitare gerarchie profonde, ma voglio chiarire che la gerarchia non è il problema; l'alto numero di dipendenze è In questo caso, C ++ non è un linguaggio che ti permette di parametrizzare una classe con la sua super classe (certamente una caratteristica abbastanza insolita), che fornisce un altro argomento contro l'utilizzo dell'ereditarietà rispetto alla composizione come meccanismo di strutturazione del codice. Una super classe è una dura dipendenza della sottoclasse.

    
risposta data 26.03.2016 - 16:18
fonte
2

Una possibilità è quella di utilizzare una gerarchia di oggetti simili ai builder:

class dependency1;
class dependency2;
class dependency3;

class ClassA {
  public:
    struct Builder {
      dependency1 *dep1 = 0;
    };

    ClassA(const Builder &builder) { ... }
};

class ClassB : public ClassA {
  public:
    struct Builder : ClassA::Builder {
      dependency2 *dep2 = 0;
    };

    ClassB(const Builder &builder) : ClassA(builder) { ... }
};


class ClassC : public ClassB {
  public:
    struct Builder : ClassB::Builder {
      dependency3 *dep3 = 0;
    };

    ClassC(const Builder &builder) : ClassB(builder) { ... }
};

E usalo in questo modo:

  ClassC::Builder builder;
  builder.dep1 = dep1;
  builder.dep2 = dep2;
  builder.dep3 = dep3;

  ClassC c(builder);

Questo non usa il tipico Fluent Interface forma di builder, perché ciò richiederebbe alcuni trucchi per farlo funzionare con una gerarchia, che probabilmente non ne vale la pena.

    
risposta data 26.03.2016 - 16:18
fonte