Lors de l'apprentissage de la programmation orientée objet, et après avoir maîtrisé les bases des objets, des champs et des méthodes, on passe la plupart du temps sur l'héritage. L'héritage signifie que nous acquérons une partie de l'implémentation d'une classe de base. Il suffit de créer une sous-classe d'une classe de base pour hériter de tous les champs et méthodes non privés.
Une voiture et un avion sont des véhicules, il est donc évident que ces deux classes doivent être développées à partir de leur classe de base commune appelée Véhicule. Il s'agit d'un exemple académique typique, mais en décidant de lier ces classes avec la relation d'héritage, nous devons être conscients de certaines conséquences.
Fig. 1 Mise en œuvre de la relation d'héritage.
Dans ce cas, les classes sont étroitement liées les unes aux autres, ce qui signifie que les modifications du comportement de chaque classe peuvent être apportées en modifiant la classe de base. code. Cela peut être un avantage comme un inconvénient - cela dépend du type de comportement que nous attendons. Si l'héritage est appliqué au mauvais moment, le processus d'ajout d'une nouvelle fonction peut se heurter à des difficultés de mise en œuvre parce qu'elle ne correspondra pas au modèle de classe créé. Nous devrons choisir entre la duplication du code et la réorganisation de notre modèle, ce qui peut prendre beaucoup de temps. Nous pouvons qualifier le code qui exécute la relation d'héritage d'"ouvert-fermé", ce qui signifie qu'il est ouvert aux extensions mais fermé aux modifications. En supposant que la classe Véhicule contienne un fonctionnement général et défini du moteur de chaque véhicule, si nous voulons ajouter un modèle de véhicule sans moteur (par exemple, un vélo) à notre hiérarchie de classes, nous devrons apporter de sérieuses modifications à nos classes.
classe Véhicule
def start_engine
end
def stop_engine
end
end
classe Plane < Véhicule
def move
start_engine
...
stop_engine
fin
fin
Composition
Si nous ne sommes intéressés que par une partie du comportement de la classe existante, une bonne alternative à l'héritage est d'utiliser la composition. Au lieu de créer des sous-classes qui héritent de tous les comportements (ceux dont nous avons besoin et ceux dont nous n'avons pas besoin du tout), nous pouvons isoler les fonctions dont nous avons besoin et équiper nos objets de références à ces fonctions. De cette manière, nous abandonnons l'idée que l'objet est un type de un objet de base, en faveur de l'affirmation selon laquelle il contient seulement certaines parties de ses propriétés.
Fig. 2 Utilisation de la composition
En suivant cette approche, nous pouvons isoler le code responsable du fonctionnement du moteur dans la classe autonome appelée Moteur et n'y faire référence que dans les classes qui représentent les véhicules dotés d'un moteur. L'isolation des fonctions grâce à la composition simplifiera la structure de la classe Véhicule et renforcera l'encapsulation des classes individuelles. Désormais, la seule façon pour les véhicules d'avoir un effet sur le moteur est d'utiliser son interface publique, car ils n'auront plus d'informations sur son implémentation. De plus, cela permettra d'utiliser différents types de moteurs dans différents véhicules, et même de les échanger pendant que le programme est en cours d'exécution. Bien sûr, l'utilisation de la composition n'est pas sans faille - nous créons un ensemble de classes faiblement connectées, qui peut être facilement étendu et qui est ouvert à la modification. Mais, en même temps, chaque classe est connectée à de nombreuses autres classes et doit disposer d'informations concernant leurs interfaces.
classe Véhicule
fin
classe Moteur
def start
end
def stop
end
fin
classe Plane < Véhicule
def initialize
@engine = Engine.new
end
def move
@engine.start
@engine.stop
end
def change_engine(new_engine)
@engine = new_engine
end
end
Le choix
Les deux approches décrites ont des avantages et des inconvénients, alors comment choisir entre elles ? L'héritage est une spécialisation, il est donc préférable de ne l'appliquer qu'aux problèmes dans lesquels il existe des relations de type "is-a" - nous avons donc affaire à la véritable hiérarchie des types. Étant donné que l'héritage lie étroitement les classes entre elles, nous devrions toujours nous demander s'il convient ou non d'utiliser la composition. La composition doit être appliquée aux problèmes dans lesquels il existe des relations de type "has-a" - la classe a donc de nombreuses parties, mais elle est quelque chose de plus qu'un ensemble de classes. Un avion est composé d'éléments, mais seul, il est quelque chose de plus - il a des capacités supplémentaires, telles que le vol. Si l'on poursuit cet exemple, les pièces individuelles peuvent se présenter sous différentes variantes spécialisées, et c'est alors le bon moment pour utiliser l'héritage.
L'héritage comme la composition ne sont que des outils que les programmeurs ont à leur disposition, donc choisir le bon outil pour un problème particulier demande de l'expérience. Alors, pratiquons et apprenons de nos erreurs 🙂 .