Principe ouvert-fermé et modificateur "Final" Java

le principe" open-closed "stipule que" les entités logicielles (classes, modules, fonctions, etc.) devrait être ouvert pour extension, mais fermé pour modification".

cependant, Joshua Bloch dans son célèbre livre "Effective Java" donne le conseil suivant: "Design and document for inheritance, or else prohibit it", et encourage les programmeurs à utiliser le modificateur "final" pour interdire le sous-titrage.

je pense que ces deux principes sont clairement en contradiction les uns les autres (je me trompe?). Quel principe suivez-vous dans l'écriture de votre code, et pourquoi? Ne vous laissez vos classes ouvertes, de rejeter l'héritage sur certains d'entre eux (et lesquelles?), ou utiliser le modificateur final dans la mesure du possible?

21
demandé sur rmaruszewski 2009-03-18 15:08:58

7 réponses

franchement, je pense que le principe ouvert/fermé est plus un anachronisme que non. Elle remonte aux années 80 et 90 quand les cadres OO ont été construits sur le principe que tout doit hériter de quelque chose d'autre et que tout devrait être sous-classable.

cela a été le plus typé dans les cadres D'UI de l'époque comme MFC et Java Swing. Dans Swing, vous avez héritage ridicule où (iirc) touche étend case à cocher (ou l'inverse) donnant à l'un d'eux comportement ce n'est pas utilisé (je pense que c'est l'appel setDisabled() sur checkbox). Pourquoi partagent-ils une ascendance? Aucune autre raison que, bien, ils avaient certaines méthodes en commun.

de nos jours, la composition est préférée à l'héritage. Alors que Java permettait l'héritage par défaut, .Net a adopté l'approche (plus moderne) de le rejeter par défaut, ce qui est, je pense, plus correct (et plus conforme aux principes de Josh Bloch).

DI / IoC ont également fait pour la composition.

Josh Bloch souligne également que l'héritage rompt l'encapsulation et donne quelques bons exemples de pourquoi. Il a également été démontré que la modification du comportement des collections Java est plus cohérente si elle est faite par délégation plutôt que par extension des classes.

personnellement, je considère en grande partie l'héritage comme peu plus qu'un détail d'implémentation ces jours-ci.

25
répondu cletus 2009-03-18 12:17:49

Je ne pense pas que les deux affirmations se contredisent. Un type peut être ouvert pour extension et encore être fermé pour héritage.

l'une des façons de le faire est d'utiliser l'injection de dépendance. Au lieu de créer des instances de ses propres types d'assistants, un type peut avoir ces fournis lors de la création. Cela vous permet de changer les parties (c'est-à-dire ouvrir pour extension) du type sans changer le type lui-même (c'est-à-dire Fermer pour modification).

8
répondu Brian Rasmussen 2009-03-18 12:39:39

Dans ouvert-fermé le principe (ouvert pour l'extension, fermé pour la modification), vous pouvez toujours utiliser le modificateur final. Voici un exemple:

public final class ClosedClass {

    private IMyExtension myExtension;

    public ClosedClass(IMyExtension myExtension)
    {
        this.myExtension = myExtension;
    }

    // methods that use the IMyExtension object

}

public interface IMyExtension {
    public void doStuff();
}

le ClosedClass est fermé pour modification à l'intérieur de la classe, mais ouvert pour extension par une autre. Dans ce cas, il peut s'agir de tout ce qui implémente l'interface IMyExtension . Cette astuce est une variante de l'injection de dépendance puisque nous alimentons la classe fermée avec un autre, dans ce cas par le constructeur. Puisque l'extension est un interface il ne peut pas être final mais sa classe d'implémentation peut l'être.

utiliser les classes final on pour les fermer en java est similaire à utiliser sealed en C#. Il y a discussions similaires à ce sujet du côté.net.

7
répondu Spoike 2017-05-23 10:29:45

de nos jours, j'utilise le modificateur final par défaut, presque de façon réflexive comme partie du boilerplate. Cela rend les choses plus faciles à raisonner, quand vous savez qu'une méthode donnée fonctionnera toujours comme vu dans le code que vous regardez en ce moment.

bien sûr, il y a parfois des situations où une hiérarchie de classe est exactement ce que vous voulez, et il serait stupide de ne pas en utiliser une alors. Mais craignez les hiérarchies de plus de deux niveaux, ou celles où non abstraites les classes sont ensuite subdivisées. Une classe doit être abstraite ou finale.

la plupart du temps, l'utilisation de la composition est la voie à suivre. Mettez toutes les machines communes dans une classe, mettez les différents cas dans des classes différentes, puis composez les instances pour avoir l'ensemble de travail.

vous pouvez appeler cela "l'injection de dépendance", ou "le modèle de stratégie" ou "le modèle de visiteur" ou n'importe quoi, mais ce qu'il se résume à utiliser la composition au lieu de l'héritage éviter la répétition.

5
répondu Zarkonnen 2009-03-18 12:32:38

je respecte beaucoup Joshua Bloch, et je considère que Java efficace est à peu près la bible Java . Mais je pense que la défaillance automatique à l'accès private est souvent une erreur. J'ai tendance à rendre les choses protected par défaut pour qu'elles puissent au moins être accessibles en étendant la classe. Cela est principalement né d'un besoin d'unité des composants de test, mais je le trouve aussi pratique pour passer outre le comportement par défaut des classes. Je je trouve cela très ennuyeux lorsque je travaille dans la base de données de ma propre entreprise et que je dois copier et modifier la source parce que l'auteur a choisi de tout "cacher". Si c'est du tout en mon pouvoir, je fais pression pour que l'accès soit changé en protected pour éviter la duplication, ce qui est bien pire IMHO.

gardez également à l'esprit que L'arrière-plan de Bloch est dans la conception très public bedrock API libraries; la barre pour obtenir un tel code "correct" doit être placé très haut, donc il y a des chances que ce ne soit pas vraiment la même situation que la plupart du code que vous allez écrire. Les bibliothèques importantes, comme la JRE elle-même, ont tendance à être plus restrictives afin de s'assurer que la langue n'est pas utilisée abusivement. Voir toutes les API dépréciées dans la JRE? Il est presque impossible de les changer ou de les enlever. Votre code n'est probablement pas gravé dans la pierre, donc vous do avez l'occasion de corriger les choses s'il s'avère que vous avez fait une erreur au départ.

3
répondu Limbic System 2009-03-18 12:20:56

les deux énoncés

entités logicielles (classes, modules, fonctions, etc.) devrait être ouvert pour extension, mais fermé pour modification.

et

dessin ou modèle et document d'héritage, ou bien l'interdire.

ne sont pas en contradiction directe les uns avec les autres. Vous pouvez suivre le principe open-closed aussi longtemps que vous concevez et document (comme par Bloch conseils).

Je ne pense pas que Bloch déclare que vous devriez préférer pour interdire l'héritage en utilisant le modificateur final, juste que vous devriez explicitement choisir d'autoriser ou de rejeter l'héritage dans chaque classe que vous créez. Son conseil est que vous devriez y réfléchir et décider par vous-même, au lieu d'accepter simplement le comportement par défaut du compilateur.

3
répondu Bill the Lizard 2009-03-18 13:18:18

Je ne pense pas que le principe ouvert/fermé tel que présenté à l'origine permette l'interprétation que les classes finales peuvent être étendues par l'injection de dépendances.

à mon avis, le principe consiste à ne pas autoriser de changements directs au code qui a été mis en production, et la façon d'y parvenir tout en permettant des modifications à la fonctionnalité est d'utiliser l'héritage de la mise en œuvre.

comme indiqué dans le première réponse, cela a des racines historiques. Il y a des décennies, l'héritage était en faveur, le test du développeur était inconnu, et la recompilation de la base de code prenait souvent trop de temps.

aussi, considérer que dans C++ les détails d'implémentation d'une classe (en particulier, les champs privés) étaient couramment exposés dans le".h " fichier d'en-tête, donc si un programmeur avait besoin de le changer, tous les clients auraient besoin de recompilation. Notez que ce n'est pas le cas avec les langues modernes comme Java ou C#. D'Ailleurs, Je ne pensez pas que les développeurs de l'époque pourraient compter sur des IDEs sophistiqués capables d'effectuer une analyse de dépendance à la volée, en évitant la nécessité de fréquentes reconstructions complètes.

D'après mon expérience, je préfère faire exactement le contraire: "les classes devraient être fermées pour extension ( final ) par défaut, mais ouvertes pour modification". Pensez-y: aujourd'hui nous favorisons des pratiques comme contrôle de version (rend facile à récupérer / comparer les versions précédentes d'un class), refactoring (qui nous encourage à modifier le code pour améliorer la conception, ou comme prélude à l'introduction de nouvelles fonctionnalités), et developer testing , qui fournit un filet de sécurité lors de la modification du code existant.

1
répondu Rogério 2009-06-27 22:50:41