Ereditarietà sbagliata

12

Ho un 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

ecc.

Hai i tuoi metodi eat (), run (), etc e tutto va bene. Poi un giorno arriva qualcuno e dice: la nostra classe CageBuilder funziona alla grande e usa animal.weight () e animal.height (), eccetto il nuovo Bison africano che è troppo strong e può frantumare il muro, quindi aggiungerò un'altra proprietà alla classe Animal - isAfricanBizon () e usala quando scegli il materiale e la sostituisci solo per la classe AfricanBizon. La prossima persona arriva e fa qualcosa di simile e la prossima cosa che sai è che tutte queste proprietà sono specifiche per alcuni sottoinsiemi della gerarchia nella classe base.

Quale è un buon modo per migliorare / rifattorizzare tale codice? Un'alternativa qui sarebbe utilizzare solo dynamic_casts per verificare i tipi, ma che ingombra i chiamanti e aggiunge un sacco di if-then-else ovunque. Puoi avere interfacce più specifiche qui, ma se tutto quello che hai è il riferimento della classe base che non aiuta molto. Qualche altro suggerimento? Esempi?

Grazie!

    
posta naumcho 15.07.2011 - 00:03
fonte

9 risposte

13

Sembra che il problema sia invece di implementare RequiresConcreteWall (), hanno implementato una chiamata flag IsAfricanBison (), quindi ha spostato la logica sull'eventualità che il muro dovesse cambiare 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 su ciò che viene detto, non sulla base di ciò che sono.

    
risposta data 15.07.2011 - 00:09
fonte
12

isAfricanBizon () non è generico. Immagina di estendere la tua fattoria degli animali con un ippopotamo che è anche troppo strong, ma restituire true da isAfricanBizon () per avere l'effetto giusto sarebbe semplicemente sciocco.

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

    
risposta data 15.07.2011 - 00:08
fonte
3

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

È questione di design dei clienti minimizzare l'uso di dynamic_cast < & gt ;; dovrebbero usarlo per determinare se l'oggetto richiede un trattamento speciale e in tal caso eseguire tutte le operazioni sul riferimento down-casted.

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

    
risposta data 15.07.2011 - 00:26
fonte
1

Una cosa che puoi fare è sostituire il controllo esplicito di tipo come isAfricanBison() con il controllo delle proprietà a cui sei realmente interessato, ovvero isTooStrong() .

    
risposta data 15.07.2011 - 00:10
fonte
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 che comunque non sia fattibile. Questo è il problema con gli esempi di giocattoli, però.

Non vorrei mai vedere RequiresConcreteWalls () o linee e linee di cast di puntatori dinamici in ogni caso.

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

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

Questo non ti impedisce di usare il codice condiviso, ma inquina un po 'Animal.

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

Ho usato Component Based Design a fini buoni, il problema principale con esso è che può essere fastidioso quando la proprietà di Animal è condivisa. Come evitare di lanciare i distruttori come punto dolente.

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

Oltre a questo è difficile indovinare il problema.

    
risposta data 15.07.2011 - 02:03
fonte
0

Beh, sicuramente tutti gli animali hanno la proprietà intrinseca di attemptEscape() . Mentre alcuni potrebbero porre un risultato di false in tutti gli scenari, mentre altri potrebbero avere una possibilità basata sull'euristica delle loro altre caratteristiche intrinseche come size e weight . Quindi sicuramente ad un certo punto attemptEscape() diventa banale dato che sicuramente restituirà true .

Temo di non comprendere 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 correlare direttamente Bison a Parrots non è una buona configurazione di ereditarietà e non dovrebbe davvero essere un problema in una corretta progettazione.

    
risposta data 15.07.2011 - 00:10
fonte
-1

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

    
risposta data 15.07.2011 - 00:51
fonte
-1

come su RecommendCageType () come oppsed a RequiresConcreteWall ()

    
risposta data 17.07.2011 - 16:33
fonte
-2

Perché non fare qualcosa di simile

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

Con la classe HeavyAnimals puoi creare la classe Bison africano estendendola la classe HeavyAnimals.

Quindi ora la classe genitore (gli Animali) che può essere usata per creare altre classi base come la classe HeavyAnimal con può essere usata per creare la classe Bison africana e altri Pesci Pesci. Quindi con il bisonte africano ora hai accesso ai metodi e alle proprietà della classe degli animali (questa è la base per tutti gli animali) e accedi alla classe HeavyAnimals (questa è la base per gli animali pesanti)

    
risposta data 17.07.2015 - 19:49
fonte

Leggi altre domande sui tag