Lors de la lecture de diverses questions Stack Overflow et du code d'autres personnes, le consensus général sur la manière de concevoir des classes est fermé. Cela signifie que, par défaut, en Java et en C #, tout est privé, les champs sont finaux, certaines méthodes sont finales et parfois les classes sont même finales .
L'idée derrière ceci est de cacher les détails de l'implémentation, ce qui est une très bonne raison. Cependant, avec la protected
plupart des langages POO et du polymorphisme, cela ne fonctionne pas.
Chaque fois que je souhaite ajouter ou modifier des fonctionnalités à une classe, des obstacles privés et définitifs sont placés partout. Ici, les détails de l'implémentation importent: vous prenez l'implémentation et l'étendez, sachant parfaitement quelles en sont les conséquences. Cependant, étant donné que je ne peux pas accéder aux champs et méthodes privés et finaux, j'ai trois options:
- Ne prolongez pas la classe, contournez simplement le problème conduisant à un code plus complexe
- Copier et coller toute la classe, éliminant ainsi la réutilisation du code
- Fork le projet
Ce ne sont pas de bonnes options. Pourquoi n'est-il pas protected
utilisé dans les projets écrits dans les langues qui le supportent? Pourquoi certains projets interdisent-ils explicitement l'héritage de leurs classes?
la source
Réponses:
Concevoir des classes pour qu'elles fonctionnent correctement lorsqu'elles sont étendues, en particulier lorsque le programmeur chargé de l'extension ne comprend pas parfaitement le fonctionnement supposé de la classe, nécessite un effort supplémentaire considérable. Vous ne pouvez pas simplement prendre tout ce qui est privé et le rendre public (ou protégé) et appeler cela "ouvert". Si vous autorisez quelqu'un d'autre à modifier la valeur d'une variable, vous devez prendre en compte l'impact de toutes les valeurs possibles sur la classe. (Que se passe-t-il si la variable est définie sur null? Un tableau vide? Un nombre négatif?) Il en va de même lorsque vous autorisez d'autres personnes à appeler une méthode. Il faut bien réfléchir.
Ce n’est donc pas tellement que les classes ne devraient pas être ouvertes, mais que parfois, cela ne vaut pas la peine de les faire ouvrir.
Bien sûr, il est également possible que les auteurs de la bibliothèque soient simplement paresseux. Cela dépend de la bibliothèque dont vous parlez. :-)
la source
Rendre tout ce qui est privé par défaut a l'air dur, mais regardez de l'autre côté: quand tout est privé par défaut, rendre quelque chose public (ou protégé, ce qui est presque la même chose) est supposé être un choix conscient; c'est le contrat de l'auteur de la classe avec vous, le consommateur, sur la manière d'utiliser la classe. C'est une commodité pour vous deux: l'auteur est libre de modifier le fonctionnement interne de la classe, tant que l'interface reste inchangée; et vous savez exactement sur quelles parties de la classe vous pouvez compter et lesquelles sont susceptibles de changer.
L'idée sous-jacente est le «couplage faible» (également appelé «interfaces étroites»); sa valeur réside dans la maîtrise de la complexité. En réduisant le nombre de façons dont les composants peuvent interagir, la quantité de dépendance réciproque entre eux est également réduite; et la dépendance croisée est l’un des pires types de complexité en matière de maintenance et de gestion du changement.
Dans les bibliothèques bien conçues, les classes qui méritent d’être étendues par héritage ont protégé les membres publics aux bons endroits et cachent tout le reste.
la source
Tout ce qui n'est pas privé est plus ou moins supposé exister avec un comportement inchangé dans toutes les versions futures de la classe. En fait, cela peut être considéré comme faisant partie de l'API, documenté ou non. Par conséquent, exposer trop de détails pose probablement des problèmes de compatibilité ultérieurement.
En ce qui concerne "final" resp. classes "scellées", un cas typique sont des classes immuables. Beaucoup de parties du framework dépendent de la nature immuable des chaînes. Si la classe String n'était pas finale, il serait facile (voire tentant) de créer une sous-classe String modifiable, qui cause toutes sortes de bugs dans de nombreuses autres classes lorsqu'elle est utilisée à la place de la classe String immuable.
la source
final
et / ouprivate
est verrouillé en place et ne peut jamais être changé .public
membres;protected
les membres ne forment un contrat qu'entre la classe qui les expose et ses classes dérivées immédiates; en revanche, lespublic
membres signent des contrats avec tous les consommateurs pour le compte de toutes les classes héritées possibles, présentes et futures.Dans OO, il existe deux manières d'ajouter des fonctionnalités au code existant.
La première est par héritage: vous prenez une classe et en dérivez. Cependant, l'héritage doit être utilisé avec précaution. Vous devez utiliser l'héritage public principalement lorsque vous avez une relation isA entre la base et la classe dérivée (par exemple, un rectangle est une forme). Au lieu de cela, vous devez éviter l'héritage public pour la réutilisation d'une implémentation existante. En principe , l'héritage public est utilisé pour s'assurer que la classe dérivée a la même interface que la classe de base (ou une classe plus grande), afin que vous puissiez appliquer le principe de substitution de Liskov .
L'autre méthode pour ajouter des fonctionnalités consiste à utiliser la délégation ou la composition. Vous écrivez votre propre classe qui utilise la classe existante en tant qu'objet interne et vous lui déléguez une partie du travail d'implémentation.
Je ne sais pas quelle était l'idée derrière toutes ces finales de la bibliothèque que vous essayez d'utiliser. Les programmeurs voulaient probablement s'assurer que vous n'allez pas hériter d'une nouvelle classe, car ce n'est pas la meilleure façon d'étendre leur code.
la source
La plupart des réponses ont raison: les classes doivent être scellées (final, NotOverridable, etc.) lorsque l'objet n'est pas conçu ni destiné à être étendu.
Cependant, je n’envisagerais pas simplement de fermer tout le code de conception approprié. Le "O" dans SOLID est pour "Principe d'ouverture-fermeture", indiquant qu'une classe doit être "fermée" à la modification, mais "ouverte" à l'extension. L'idée est que vous avez du code dans un objet. Cela fonctionne très bien. L'ajout de fonctionnalités ne doit pas obliger à ouvrir ce code et à effectuer des modifications chirurgicales susceptibles de rompre un comportement antérieur au travail. Au lieu de cela, on devrait pouvoir utiliser l'injection d'héritage ou de dépendance pour "étendre" la classe afin qu'elle fasse des choses supplémentaires.
Par exemple, une classe "ConsoleWriter" peut obtenir du texte et l'afficher sur la console. Il fait ça bien. Cependant, dans certains cas, vous avez également besoin de la même sortie écrite dans un fichier. Ouvrir le code de ConsoleWriter, changer son interface externe pour ajouter un paramètre "WriteToFile" aux fonctions principales, et placer le code d'écriture de fichier supplémentaire à côté du code d'écriture de console serait généralement considéré comme une mauvaise chose.
Au lieu de cela, vous pouvez effectuer l'une des opérations suivantes: vous pouvez dériver de ConsoleWriter pour former ConsoleAndFileWriter et étendre la méthode Write () pour appeler d'abord l'implémentation de base, puis écrire dans un fichier. Vous pouvez également extraire une interface IWriter de ConsoleWriter et la réimplémenter pour créer deux nouvelles classes. un FileWriter et un "MultiWriter" auxquels d'autres IWriters peuvent être donnés et qui "diffuseront" tout appel de ses méthodes à tous les rédacteurs donnés. Le choix que vous choisirez dépendra de ce dont vous aurez besoin. La dérivation simple est, bien, plus simple, mais si vous avez l’impression d’avoir éventuellement besoin d’envoyer le message à un client réseau, trois fichiers, deux canaux nommés et la console, continuez et extrayez l’interface et créez le "Adaptateur en Y"; il'
Maintenant, si la fonction Write () n'a jamais été déclarée virtuelle (ou si elle était scellée), vous avez maintenant des problèmes si vous ne contrôlez pas le code source. Parfois même si vous le faites. Il s’agit généralement d’une mauvaise position, et les utilisateurs d’API à source fermée ou à source limitée sont sans cesse frustrés. Mais, il existe une raison légitime pour laquelle Write (), ou toute la classe ConsoleWriter, est scellé; il peut y avoir des informations sensibles dans un champ protégé (substituées à leur tour par une classe de base pour fournir lesdites informations). La validation peut être insuffisante; lorsque vous écrivez une API, vous devez supposer que les programmeurs qui utilisent votre code ne sont ni plus intelligents ni bienveillants que "l'utilisateur final" moyen du système final; De cette façon, vous ne serez jamais déçu.
la source
Je pense que cela vient probablement de toutes les personnes utilisant un langage oo pour la programmation en c. Je te regarde, java. Si votre marteau a la forme d'un objet, mais que vous souhaitez un système procédural et / ou ne comprend pas vraiment les classes, votre projet devra alors être verrouillé.
Fondamentalement, si vous allez écrire dans une langue oo, votre projet doit suivre le paradigme oo, qui inclut l'extension. Bien sûr, si quelqu'un a écrit une bibliothèque dans un paradigme différent, peut-être que vous n'êtes pas censé l'étendre de toute façon.
la source
Parce qu'ils ne savent pas mieux.
Les auteurs originaux s’attachent probablement à une incompréhension des principes SOLID, qui trouve son origine dans un monde C ++ confus et compliqué.
J'espère que vous remarquerez que les mondes ruby, python et perl n'ont pas les problèmes que les réponses prétendent être la raison de la fermeture. Notez que c'est orthogonal au typage dynamique. Les modificateurs d'accès sont faciles à utiliser dans la plupart des langues (toutes?). Les champs C ++ peuvent être mélangés avec un autre type (le C ++ est plus faible). Java et C # peuvent utiliser la réflexion. Les modificateurs d'accès rendent les choses suffisamment difficiles pour vous empêcher de le faire à moins que vous ne le souhaitiez VRAIMENT.
Le fait de sceller des classes et de marquer tout membre privé enfreint explicitement le principe selon lequel les choses simples doivent être simples et les choses difficiles doivent être possibles. Soudain, les choses qui devraient être simples ne le sont pas.
Je vous encourage à essayer de comprendre le point de vue des auteurs originaux. Cela provient en grande partie d'une idée académique d'encapsulation qui n'a jamais démontré un succès absolu dans le monde réel. Je n'ai jamais vu un framework ou une bibliothèque où un développeur, quelque part, ne souhaitait pas fonctionner de manière légèrement différente et n'avait aucune raison de le modifier. Il y a deux possibilités qui ont pu nuire aux développeurs de logiciels originaux qui ont scellé et rendu leurs membres privés.
Je pense que dans le cadre institutionnel, le n ° 2 est probablement le cas. Ces frameworks C ++, Java et .NET doivent être "terminés" et doivent respecter certaines directives. Ces instructions désignent généralement les types scellés, à moins que le type n'ait été explicitement conçu comme faisant partie d'une hiérarchie de types et de membres privés pour de nombreux éléments pouvant être utiles à d'autres utilisateurs .. mais comme ils ne sont pas directement liés à cette classe, ils ne sont pas exposés. . Extraire un nouveau type serait trop coûteux à prendre en charge, documenter, etc.
L'idée derrière les modificateurs d'accès est que les programmeurs devraient être protégés d'eux-mêmes. "La programmation en C est mauvaise car elle vous permet de vous tirer une balle dans le pied." Ce n'est pas une philosophie avec laquelle je suis d'accord en tant que programmeur.
Je préfère de loin l'approche du nom python. Vous pouvez facilement (beaucoup plus facile que la réflexion) remplacer des soldats si vous en avez besoin. Un bon article est disponible ici: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/
Le modificateur privé de Ruby ressemble en réalité davantage à une protection en C # et n'a pas de modificateur privé en tant que C #. Protégé est un peu différent. Il y a une grande explication ici: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
N'oubliez pas que votre langage statique ne doit pas nécessairement se conformer aux styles désuets du code écrit dans ce langage précédent.
la source
EDIT: Je crois que les cours devraient être conçus pour être ouverts. Avec certaines restrictions, mais ne devrait pas être fermé pour héritage.
Pourquoi: "classes finales" ou "classes scellées" me semble étrange. Je n'ai jamais à marquer une de mes propres classes comme "finale" ("scellée"), car je pourrais devoir hériter de ces classes, plus tard.
J'ai acheté / téléchargé des bibliothèques tierces avec des classes (la plupart des contrôles visuels), et je suis vraiment reconnaissant qu'aucune de ces bibliothèques n'utilise "final", même si elle est supportée par le langage de programmation dans lequel elles ont été codées, car j'ai fini d'étendre ces classes, même si j'ai compilé des bibliothèques sans le code source.
Parfois, je dois traiter avec certaines classes "scellées" occasionnelles, et j'ai fini par créer une nouvelle classe "wrapper" contenant la classe donnée, qui a des membres similaires.
Les classificateurs Scope permettent de "sceller" certaines fonctionnalités sans restreindre l'héritage.
Quand j'ai changé de programme structuré. À la programmation orientée objet, j'ai commencé à utiliser des membres de classe privés , mais après de nombreux projets, j'ai fini par utiliser protected et, éventuellement, je fais la promotion de ces propriétés ou méthodes au public , si nécessaire.
PS Je n'aime pas le mot-clé "final" en C #, mais plutôt "scellé" comme Java ou "stérile", selon la métaphore de l'héritage. En outre, "final" est un mot ambigu utilisé dans plusieurs contextes.
la source