Eredità andata storta


12

Ho del codice in cui un buon modello di ereditarietà è andato in discesa e sto cercando di capire perché e come risolverlo. Fondamentalmente, immagina di avere una gerarchia Zoo con:

class Animal  
class Parrot : Animal 
class Elephant : Animal 
class Cow : Animal

eccetera.

Hai i tuoi metodi eat (), run (), ecc. E tutto va bene. Poi un giorno qualcuno arriva e dice: la nostra classe CageBuilder funziona alla grande e usa animal.weight () e animal.height (), ad eccezione del nuovo bisonte africano che è troppo forte e può mandare in frantumi il muro, quindi aggiungerò un'altra proprietà per la classe Animal - isAfricanBizon () e usala quando scegli il materiale e lo ignori solo per la classe AfricanBizon. La persona successiva arriva e fa qualcosa di simile e la prossima cosa che sai di avere tutte queste proprietà specifiche per alcuni sottogruppi della gerarchia nella classe base.

Qual è un buon modo per migliorare / refactoring tale codice? Un'alternativa qui sarebbe usare solo dynamic_casts per verificare i tipi, ma ciò ingombra i chiamanti e aggiunge un sacco di if-then-else in tutto il luogo. Puoi avere interfacce più specifiche qui, ma se tutto ciò che hai è il riferimento della classe base che non aiuta molto. Altri suggerimenti? Esempi?

Grazie!


@James: allora dovrai scrivere i parser a mano. : S
Matteo Italia,

5
Chiaramente questo è un caso di esigenza ridicola del cliente. Non ci sono bisonti in Africa. Non è possibile progettare modelli di oggetti che non hanno alcuna connessione con la realtà. A meno che quella realtà non sia creata da mani piene di dollari. Il che risolve il problema.
Hans Passant,

1
Mangia tutto il bisonte? [L'ho pubblicato in precedenza, ma per qualche motivo è stato eliminato, presumibilmente da idioti senza umorismo.]
James McNellis,

CageBuilder ha bisogno della sua classe? Cosa succede se esiste un metodo MakeCage predefinito, che può essere sostituito da ogni singola classe.
Giobbe

1
Si menziona il disordine if-then-else come svantaggio per i chiamanti, ma non appena i chiamanti iniziano a utilizzare isAfricanBizon (), ingombrano automaticamente il codice con if-then-else. Quindi è un disordine if-then-else con isAfricanBizon () o un disordine if-then-else con cast dinamici.
davidk01,

Risposte:


13

Sembra che il problema sia invece di implementare RequistConcreteWall (), hanno implementato una chiamata flag IsAfricanBison () e quindi spostato la logica sull'opportunità o meno di modificare il muro al di fuori dell'ambito della classe. Le tue classi dovrebbero esporre comportamenti e requisiti, non identità; i tuoi consumatori di queste classi dovrebbero lavorare in base a ciò che viene loro detto, non in base a ciò che sono.


1
-1: dice solo cosa non fare. L'OP sa già che questa è stata una cattiva idea, quindi la domanda.
Steven Evers,

12

isAfricanBizon () non è generico. Supponiamo che estendi la tua fattoria con un ippopotamo che sia anche troppo forte ma che ritorni vero da isAfricanBizon () per avere l'effetto giusto sarebbe semplicemente sciocco.

vuoi sempre aggiungere metodi all'interfaccia che rispondano alla domanda specifica, in questo caso sarebbe qualcosa di simile a forza ()


+1: tutti gli altri sembrano infrangere il modello concettuale della classe (che incapsula le proprietà di diversi tipi di animali), per accogliere questo specifico caso d'uso. Un strengthmetodo potrebbe essere interrogato material.canHold(animal), consentendo un modo pulito di supportare diversi tipi di materiale rispetto a ConcreteWall.
Aidan Cully,

Mi piace l'approccio della proprietà strength () meglio del suggerimento degli altri di RequConcreteWall () perché è più flessibile per consentire requisiti futuri. Per i principianti, fai decidere alla classe CageBuilder quali materiali sono abbastanza forti, quindi puoi facilmente estendere la classe con nuovi materiali.
deridere il

3

Penso che il tuo problema sia questo: hai vari client della libreria che sono interessati solo a un sottoinsieme della gerarchia ma a cui viene passato un puntatore / riferimento alla classe base. Questo è in effetti il ​​problema che dynamic_cast <> è lì per risolvere.

È una questione di progettazione dei client ridurre al minimo l'uso di dynamic_cast <>; dovrebbero usarlo per determinare se l'oggetto richiede un trattamento speciale e in tal caso tutte le operazioni sul riferimento verso il basso.

Se si dispone di raccolte di funzionalità "mix-in" che si applicano a più sotto-gerarchie separate, è possibile utilizzare il modello di interfaccia utilizzato da Java e C #; avere una classe di base virtuale che è una classe pura-virtuale e utilizzare dynamic_cast <> per determinare se un'istanza fornisce un'implementazione per essa.


1

Una cosa che puoi fare è sostituire il controllo esplicito del tipo come isAfricanBison()con il controllo delle proprietà che ti interessano, ad es isTooStrong().


1
isTooStrong () per cosa? Stai aggiungendo un codice specifico della gabbia alla classe animale.
Steven Evers,

1

Gli animali non dovrebbero preoccuparsi dei muri di cemento. Forse puoi esprimerlo con valori semplici.

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

Sospetto però che non sia fattibile. Questo è il problema con esempi di giocattoli, però.

In ogni caso, non vorrei mai vedere RequistConcreteWalls () o linee e linee di lanci di puntatori dinamici.

Questa è di solito una soluzione economica . È facile da mantenere e concettualizzare. E davvero, il problema afferma che è comunque legato al tipo di animale.

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

Questo non ti preclude nemmeno l'uso del codice condiviso, inquina solo un po 'Animal.

Ma come viene costruita la gabbia può essere una politica di qualche altro sistema e forse hai più di un tipo di generatore di gabbie per animale. Ci sono molte combinazioni strane e contorte che puoi inventare.

Ho usato il Component Based Design per fini positivi, il problema principale è che può essere problematico quando la proprietà di Animal è condivisa. Come evitare il lancio di distruttori è il punto dolente.

Double Dispatch è un'altra opzione, anche se sono sempre stato reticente a saltarci dentro.

Oltre a ciò, è difficile indovinare il problema.


0

Bene sicuramente tutti gli animali hanno la proprietà intrinseca di attemptEscape(). Mentre alcuni il metodo può comportare un falserisultato in tutti gli scenari, mentre altri possono avere una possibilità basata sull'euristica delle loro altre caratteristiche intrinseche come sizee weight. Quindi sicuramente ad un certo punto attemptEscape()diventa banale in quanto sicuramente ritornerà true.

Temo di non capire completamente la tua domanda però ... tutti gli animali hanno azioni e caratteristiche correlate. Quelli specifici per l'animale dovrebbero essere introdotti dove è appropriato. Cercare di mettere in relazione diretta Bison con Parrots non è una buona configurazione di ereditarietà e non dovrebbe davvero essere un problema in una progettazione corretta.


-1

Un'altra opzione sarebbe quella di utilizzare una fabbrica che crea gabbie appropriate per ogni animale. Penso che questo possa essere migliore nel caso in cui le condizioni siano molto diverse per ciascuno di essi. Ma se è solo questa condizione il RequiresConcreteWall()metodo sopra menzionato lo farà.


-1

che ne dici di AdviceCageType () rispetto a RequistConcreteWall ()


-2

Perché non fare qualcosa del genere

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

Con la classe HeavyAnimals puoi creare la classe African Bison estendendo la classe HeavyAnimals.

Quindi ora puoi utilizzare la classe genitore (gli Animali) che puoi usare per creare un'altra classe base come la classe HeavyAnimal con cui puoi creare la classe Bisonte africano e altri animali pesanti. Quindi con il bisonte africano ora hai accesso ai metodi e alle proprietà della classe Animale (questa è la base per tutti gli animali) e l'accesso alla classe HeavyAnimals (questa è la base per gli animali pesanti)


2
Potrebbe funzionare come un mix-in o un tratto, ma certamente non come una sottoclasse. Ciò richiede solo eredità multipla la prossima volta che è necessaria un'altra proprietà.
Ordous,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.