Plus j'en apprends sur les différents paradigmes de programmation, tels que la programmation fonctionnelle, plus je commence à remettre en question la sagesse des concepts de POO comme l'héritage et le polymorphisme. J'ai appris pour la première fois l'héritage et le polymorphisme à l'école, et à l'époque le polymorphisme semblait être une merveilleuse façon d'écrire du code générique qui permettait une extensibilité facile.
Mais face au typage du canard (à la fois dynamique et statique) et aux caractéristiques fonctionnelles telles que les fonctions d'ordre supérieur, j'ai commencé à considérer l'héritage et le polymorphisme comme imposant une restriction inutile basée sur un ensemble fragile de relations entre les objets. L'idée générale derrière le polymorphisme est que vous écrivez une fonction une fois, et que vous pouvez ensuite ajouter de nouvelles fonctionnalités à votre programme sans changer la fonction d'origine - tout ce que vous devez faire est de créer une autre classe dérivée qui implémente les méthodes nécessaires.
Mais c'est beaucoup plus simple à réaliser grâce à la frappe de canard, que ce soit dans un langage dynamique comme Python ou un langage statique comme C ++.
À titre d'exemple, considérons la fonction Python suivante, suivie de son équivalent C ++ statique:
def foo(obj):
obj.doSomething()
template <class Obj>
void foo(Obj& obj)
{
obj.doSomething();
}
L'équivalent OOP serait quelque chose comme le code Java suivant:
public void foo(DoSomethingable obj)
{
obj.doSomething();
}
La différence majeure, bien sûr, est que la version Java nécessite la création d'une interface ou d'une hiérarchie d'héritage avant de fonctionner. La version Java implique donc plus de travail et est moins flexible. De plus, je trouve que la plupart des hiérarchies d'héritage du monde réel sont quelque peu instables. Nous avons tous vu les exemples artificiels de formes et d'animaux, mais dans le monde réel, à mesure que les besoins de l'entreprise changent et que de nouvelles fonctionnalités sont ajoutées, il est difficile de faire un travail avant d'avoir vraiment besoin d'étirer la relation "est-un" entre sous-classes, ou bien remodeler / refactoriser votre hiérarchie pour inclure d'autres classes de base ou interfaces afin de répondre aux nouvelles exigences. Avec la saisie de canard, vous n'avez pas à vous soucier de modéliser quoi que ce soit - vous vous inquiétez simplement des fonctionnalités dont vous avez besoin.
Pourtant, l'héritage et le polymorphisme sont si populaires que je doute qu'il serait exagéré de les appeler la stratégie dominante d'extensibilité et de réutilisation du code. Alors, pourquoi l'héritage et le polymorphisme sont-ils si couronnés de succès? Suis-je en train de négliger certains avantages sérieux de l'héritage / polymorphisme par rapport au typage canard?
la source
obj
n'y a pas dedoSomething
méthode? Une exception est-elle levée? Il ne se passe rien?Réponses:
Je suis surtout d'accord avec vous, mais pour le plaisir, je jouerai Devil's Advocate. Les interfaces explicites permettent de rechercher un contrat explicitement et formellement spécifié, vous indiquant ce qu'un type est censé faire. Cela peut être important lorsque vous n'êtes pas le seul développeur d'un projet.
De plus, ces interfaces explicites peuvent être implémentées plus efficacement que le typage canard. Un appel de fonction virtuel a à peine plus de surcharge qu'un appel de fonction normal, sauf qu'il ne peut pas être inséré. Le typage de canard a des frais généraux substantiels. Le typage structurel de style C ++ (à l'aide de modèles) peut générer d'énormes quantités de ballonnement de fichier objet (car chaque instanciation est indépendante au niveau du fichier objet) et ne fonctionne pas lorsque vous avez besoin de polymorphisme au moment de l'exécution, pas de compilation.
Conclusion: je conviens que l'héritage et le polymorphisme de style Java peuvent être un PITA et que les alternatives doivent être utilisées plus souvent, mais elles ont toujours leurs avantages.
la source
L'héritage et le polymorphisme sont largement utilisés car ils fonctionnent pour certains types de problèmes de programmation.
Ce n'est pas qu'ils sont largement enseignés dans les écoles, c'est à l'envers: ils sont largement enseignés dans les écoles parce que les gens (alias le marché) ont constaté qu'ils fonctionnaient mieux que les anciens outils, et donc les écoles ont commencé à les enseigner. [Anecdote: lorsque j'ai commencé à apprendre la POO, il était extrêmement difficile de trouver un collège qui enseignait n'importe quelle langue de POO. Dix ans plus tard, il était difficile de trouver un collège qui n'enseignait pas une langue POO.]
Tu as dit:
Je dis:
Non, ce n'est pas
Ce que vous décrivez n'est pas le polymorphisme, mais l'héritage. Pas étonnant que vous ayez des problèmes de POO! ;-)
Reculez d'une étape: le polymorphisme est un avantage du passage de message; cela signifie simplement que chaque objet est libre de répondre à un message à sa manière.
Alors ... Duck Typing est (ou plutôt, permet) le polymorphisme
L'essentiel de votre question ne semble pas être que vous ne comprenez pas la POO ou que vous ne l'aimez pas, mais que vous n'aimez pas définir des interfaces . C'est très bien, et tant que vous faites attention, tout fonctionnera bien. L'inconvénient est que si vous avez fait une erreur - omis une méthode, par exemple - vous ne le découvrirez qu'au moment de l'exécution.
C'est une chose statique vs dynamique, qui est une discussion aussi ancienne que Lisp, et non limitée à la POO.
la source
Pourquoi?
L'héritage (avec ou sans typage canard) assure la réutilisation d'une caractéristique commune. S'il est courant, vous pouvez vous assurer qu'il est réutilisé de manière cohérente dans les sous-classes.
C'est tout. Il n'y a pas de "restriction inutile". C'est une simplification.
Le polymorphisme, de la même manière, est ce que signifie "typage du canard". Mêmes méthodes. Beaucoup de classes avec une interface identique, mais des implémentations différentes.
Ce n'est pas une restriction. C'est une simplification.
la source
L'héritage est abusé, mais il en va de même pour la frappe de canard. Les deux peuvent entraîner des problèmes.
Avec un typage fort, vous obtenez de nombreux "tests unitaires" au moment de la compilation. Avec la frappe de canard, vous devez souvent les écrire.
la source
L'avantage d'apprendre des choses à l'école, c'est que vous les apprenez. Ce qui n'est pas si bon, c'est que vous pouvez les accepter un peu trop dogmatiquement, sans comprendre quand ils sont utiles ou non.
Ensuite, si vous l'avez appris dogmatiquement, vous pourrez plus tard "vous rebeller" tout aussi dogmatiquement dans l'autre sens. Ce n'est pas bon non plus.
Comme pour toutes ces idées, il est préférable d'adopter une approche pragmatique. Développer une compréhension de leur place et de leur échec. Et ignorez toutes les façons dont ils ont été survendus.
la source
Oui, le typage statique et les interfaces sont des restrictions. Mais tout depuis que la programmation structurée a été inventée (c'est-à-dire "goto considéré comme nuisible") a été de nous contraindre. Oncle Bob a une excellente interprétation de cela dans son blog vidéo .
Maintenant, on peut affirmer que la constriction est mauvaise, mais d'un autre côté, elle apporte de l'ordre, du contrôle et de la familiarité à un sujet par ailleurs très complexe.
Assouplir les contraintes en (re) introduisant la frappe dynamique et même l'accès direct à la mémoire est un concept très puissant, mais il peut aussi rendre la tâche de nombreux programmeurs plus difficile à gérer. En particulier, les programmeurs comptaient sur le compilateur et sur la sécurité de type pour une grande partie de leur travail.
la source
L'héritage est une relation très forte entre deux classes. Je ne pense pas que Java soit plus fort. Par conséquent, vous ne devez l'utiliser que lorsque vous le pensez. L'héritage public est une relation «est-un», et non pas «habituellement est-un». Il est vraiment, vraiment facile de surexploiter l'héritage et de se retrouver avec un gâchis. Dans de nombreux cas, l'héritage est utilisé pour représenter "a-un" ou "prend-fonctionnalité-de-a", et cela est généralement mieux fait par composition.
Le polymorphisme est une conséquence directe de la relation «est-un». Si Derived hérite de Base, alors chaque Derived "est-une" Base, et donc vous pouvez utiliser un Derived partout où vous utiliseriez une Base. Si cela n'a pas de sens dans une hiérarchie d'héritage, alors la hiérarchie est fausse et il y a probablement trop d'héritage en cours.
La saisie de canard est une fonctionnalité intéressante, mais le compilateur ne vous avertira pas si vous en abusez. Si vous ne voulez pas avoir à gérer les exceptions au moment de l'exécution, vous devez vous assurer que vous obtenez les bons résultats à chaque fois. Il peut être plus simple de définir une hiérarchie d'héritage statique.
Je ne suis pas un vrai fan du typage statique (je considère que c'est souvent une forme d'optimisation prématurée), mais cela élimine certaines classes d'erreurs, et beaucoup de gens pensent que ces classes valent bien l'élimination.
Si vous préférez le typage dynamique et le typage canard au typage statique et aux hiérarchies d'héritage définies, c'est bien. Cependant, la méthode Java a ses avantages.
la source
J'ai remarqué que plus j'utilise de fermetures C #, moins je fais de POO traditionnel. L'héritage était le seul moyen de partager facilement l'implémentation, donc je pense qu'il était souvent surutilisé et que les limites de la conception étaient trop poussées.
Bien que vous puissiez généralement utiliser les fermetures pour faire la plupart de ce que vous feriez avec l'héritage, cela peut aussi devenir laid.
Fondamentalement, c'est une situation idéale pour l'emploi: la POO traditionnelle peut très bien fonctionner lorsque vous avez un modèle qui lui convient et les fermetures peuvent très bien fonctionner lorsque vous ne le faites pas.
la source
La vérité se situe quelque part au milieu. J'aime la façon dont C # 4.0 étant un langage typé statiquement prend en charge la "saisie de canard" par mot-clé "dynamique".
la source
L'hérédité, même lorsque la raison du point de vue de la PF, est un excellent concept; non seulement il fait gagner beaucoup de temps, mais il donne un sens à la relation entre certains objets dans votre programme.
Ici, la classe
GoldenRetriever
a la même choseSound
queDog
pour les remerciements gratuits à l'héritage.Je vais écrire le même exemple avec mon niveau de Haskell pour que vous puissiez voir la différence
Ici, vous n'échappez pas à la spécification
sound
deGoldenRetriever
. Le plus simple serait en général demais imaginez si vous avez 20 fonctions! S'il y a un expert Haskell, veuillez nous montrer un moyen plus simple.
Cela dit, ce serait génial d'avoir à la fois la correspondance de modèles et l'héritage, où une fonction serait par défaut la classe de base si l'instance actuelle n'a pas l'implémentation.
la source
Animal
c'est l'anti-tutoriel de la POO.