L'héritage a mal tourné

12

J'ai un code où un bon modèle d'héritage s'est dégradé et j'essaie de comprendre pourquoi et comment y remédier. Fondamentalement, imaginez que vous ayez une hiérarchie Zoo avec:

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

etc.

Vous avez vos méthodes eat (), run (), etc. et tout va bien. Puis un jour, quelqu'un arrive et dit - notre classe CageBuilder fonctionne très bien et utilise les animaux.weight () et animal.height (), à l'exception du nouveau bison d'Afrique qui est trop fort et peut briser le mur, donc je vais ajouter une propriété de plus pour la classe Animal - isAfricanBizon () et utilisez-la lors du choix du matériau et remplacez-la uniquement pour la classe AfricanBizon. La personne suivante vient et fait quelque chose de similaire et la prochaine chose que vous savez, c'est que toutes ces propriétés sont spécifiques à un sous-ensemble de la hiérarchie dans la classe de base.

Quelle est la bonne façon d'améliorer / refactoriser un tel code? Une alternative ici serait d'utiliser simplement dynamic_casts pour vérifier les types mais cela encombre les appelants et ajoute un tas de if-then-else partout. Vous pouvez avoir des interfaces plus spécifiques ici, mais si tout ce que vous avez est la référence de classe de base qui n'aide pas beaucoup non plus. D'autres suggestions? Exemples?

Merci!

naumcho
la source
@James: alors vous devrez écrire des analyseurs à la main. : S
Matteo Italia
5
Il s'agit clairement d'un cas d'exigence ridicule du client. Il n'y a pas de bison en Afrique. Vous ne pouvez pas concevoir des modèles d'objets qui n'ont aucun lien avec la réalité. À moins que cette réalité ne soit créée par des mains pleines de dollars. Ce qui résout le problème.
Hans Passant
1
Manger tous les bisons? [J'ai posté cela précédemment mais il a été supprimé pour une raison inconnue, probablement par des crétins sans humour.]
James McNellis
CageBuilder a-t-il besoin de sa propre classe? Que faire s'il existe une méthode MakeCage par défaut, qui peut être remplacée par chaque classe individuelle.
Job
1
Vous mentionnez l'encombrement if-then-else comme un inconvénient pour les appelants mais dès que les appelants commencent à utiliser isAfricanBizon (), ils encombrent automatiquement le code avec if-then-else. Il s'agit donc soit d'un encombrement if-then-else avec isAfricanBizon (), soit d'un encombrement if-then-else avec des conversions dynamiques.
davidk01

Réponses:

13

Il semble que le problème soit au lieu d'implémenter requiresConcreteWall (), ils ont implémenté un indicateur d'appel IsAfricanBison (), puis ont déplacé la logique pour savoir si le mur devait ou non changer en dehors de la portée de la classe. Vos cours doivent exposer le comportement et les exigences, pas l'identité; vos consommateurs de ces classes devraient travailler à partir de ce qu'on leur dit, et non en fonction de ce qu'ils sont.

Josh
la source
1
-1: Ne dit que quoi ne pas faire. Le PO sait déjà que c'était une mauvaise idée, d'où la question.
Steven Evers
12

isAfricanBizon () n'est pas générique. Supposons que vous étendez votre ferme d'animaux avec un hyppopotame qui est également trop fort, mais que retourner vrai depuis isAfricanBizon () pour avoir le bon effet serait tout simplement idiot.

vous souhaitez toujours ajouter des méthodes à l'interface qui répondent à la question spécifique, dans ce cas, ce serait quelque chose comme la force ()

Karoly Horvath
la source
+1: Tout le monde semble rompre le modèle conceptuel de la classe (qui encapsule simplement les propriétés de différents types d'animaux), pour s'adapter à ce cas d'utilisation spécifique. Une strengthméthode pourrait être interrogée material.canHold(animal), permettant une manière propre de supporter différents types de matériaux ConcreteWall.
Aidan Cully
J'aime l'approche de la propriété de force () mieux que la suggestion des autres de RequirementsConcreteWall () car elle est plus flexible pour permettre les exigences futures. Pour commencer, faites en sorte que la classe CageBuilder décide quels matériaux sont suffisamment solides, puis vous pouvez facilement étendre la classe avec de nouveaux matériaux.
jhocking
3

Je pense que votre problème est le suivant: vous avez divers clients de la bibliothèque qui ne sont intéressés que par un sous-ensemble de la hiérarchie mais qui reçoivent un pointeur / référence vers la classe de base. C'est en fait le problème que dynamic_cast <> est là pour résoudre.

C'est une question de conception des clients pour minimiser l'utilisation de dynamic_cast <>; ils doivent l'utiliser pour déterminer si l'objet nécessite un traitement spécial et, dans l'affirmative, effectuer toutes les opérations sur la référence convertie.

Si vous disposez de collections de fonctionnalités de type "mix-in" qui s'appliquent à plusieurs sous-hiérarchies distinctes, vous pouvez utiliser le modèle d'interface utilisé par Java et C #; avoir une classe de base virtuelle qui est une classe virtuelle pure et utiliser dynamic_cast <> pour déterminer si une instance fournit une implémentation pour elle.

antlersoft
la source
1

Une chose que vous pouvez faire est de remplacer la vérification explicite de type comme isAfricanBison()par la vérification des propriétés qui vous intéressent réellement, c'est-à-dire isTooStrong().

hammar
la source
1
isTooStrong () pour quoi? Vous ajoutez un code spécifique à la cage à la classe animale.
Steven Evers
1

Les animaux ne devraient pas se soucier des murs en béton. Vous pouvez peut-être l'exprimer avec des valeurs simples.

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);

Je soupçonne que ce n'est pas viable cependant. C'est le problème avec les exemples de jouets, cependant.

Je ne voudrais jamais voir des RequestsConcreteWalls () ou des lignes et des lignes de pointeurs dynamiques en tout cas.

Il s'agit généralement d'une solution bon marché . C'est facile à entretenir et à conceptualiser. Et vraiment, le problème déclare que son lié au type animal de toute façon.

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

Cela ne vous empêche pas non plus d'utiliser du code partagé, pollue juste un peu Animal.

Mais la façon dont la cage est construite peut être une politique d'un autre système, et vous avez peut-être plus d'un type de constructeur de cage par animal. Il existe de nombreuses combinaisons étranges et alambiquées.

J'ai utilisé la conception basée sur les composants à de bonnes fins, le principal problème est que cela peut être gênant lorsque la propriété d'Animal est partagée. Comment éviter de jeter des destructeurs étant le point douloureux.

Double Dispatch est une autre option, même si j'ai toujours été réticent à y sauter.

Au-delà de cela, il est difficile de deviner le problème.

Tom Kerr
la source
0

Bien sûr, tous les animaux ont la propriété inhérente de attemptEscape(). Alors que certains la méthode peut poser un falserésultat dans tous les scénarios tandis que d'autres peuvent avoir une chance basée sur l'heuristique de leurs autres caractéristiques intrinsèques telles que sizeet weight. Puis, à un moment donné, cela attemptEscape()devient trivial, car il reviendra très certainement true.

J'ai bien peur de ne pas comprendre complètement votre question ... tous les animaux ont des actions et des caractéristiques liées. Ceux spécifiques à l'animal doivent être introduits là où il convient. Essayer de relier directement Bison à Parrots n'est pas une bonne configuration d'hérédité et ne devrait vraiment pas être un problème dans une conception appropriée.


la source
-1

Une autre option serait d'utiliser une usine qui crée des cages adaptées à chaque animal. Je pense que cela peut être mieux dans le cas où les conditions sont très différentes pour chacun d'eux. Mais si c'est juste cette condition, la RequiresConcreteWall()méthode mentionnée ci-dessus le fera.

RocketR
la source
-1

que diriez-vous de recommanderCageType () comme oppsed à requiertConcreteWall ()

Crétins
la source
-2

Pourquoi ne pas faire quelque chose comme ça

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

Avec la classe HeavyAnimals, vous pouvez créer la classe African Bison en étendant la classe HeavyAnimals.

Alors maintenant, vous la classe parente (les animaux) qui peut être utilisée pour créer une autre classe de base comme la classe HeavyAnimal avec peut être utilisée pour créer la classe du bison africain et d'autres animaux lourds. Donc, avec le bison d'Afrique, vous avez maintenant accès aux méthodes et aux propriétés de la classe Animal (c'est la base pour tous les animaux) et l'accès à la classe HeavyAnimals (c'est la base pour les animaux lourds)

mohhamed rafi
la source
2
Cela peut fonctionner comme un mélange ou un trait, mais certainement pas comme une sous-classe. Cela ne fait que demander l'héritage multiple la prochaine fois qu'une autre propriété est nécessaire.
Ordous