La portée au niveau du package Java est-elle utile?

11

Je comprends l'idée de la portée du package et j'ai même parfois pensé que je le voulais. Cependant, chaque fois que je m'installais avec une intention sérieuse d'essayer de l'utiliser, je découvrais qu'il ne répondait pas aux besoins que je pensais qu'il servirait.

Mon principal problème semble toujours être que les choses dont je souhaite limiter la portée ne sont jamais dans le même paquet. Conceptuellement, ils peuvent tous être liés, mais la division logique des données au sein de l'application les a comme packages enfants séparés d'un package plus grand.

Par exemple, je peux avoir un modèle de mission et je veux que seuls d'autres outils de mission, comme mes missionServices, utilisent certaines méthodes. Cependant, je me retrouve avec Missions.models et Missions.services comme mes packages, donc MissionModel et MissionService ne sont pas la même étendue de package. Il ne semble jamais qu'il y ait une situation où les packages contiennent de manière appropriée les choses que je voudrais avoir des autorisations élevées sans également inclure de nombreuses choses que je ne souhaite pas avoir ces autorisations; et je ressens rarement l’avantage de l’étendue d’un package qui justifie de modifier l’architecture de mon projet pour tout placer dans le même package. Souvent, soit des aspects ou une sorte d'inversion de contrôle s'avèrent être la meilleure approche pour tout problème pour lequel j'ai brièvement envisagé la portée du package.

Je suis curieux plutôt que cela soit généralement considéré comme vrai pour tous les développeurs Java, ou n'est qu'un coup d'œil du travail que je fais. La portée du package est-elle beaucoup utilisée dans le monde réel? Y a-t-il de nombreux cas où il est considéré comme une bonne forme à utiliser, ou est-il considéré principalement comme un comportement hérité qui est rarement exploité dans le développement moderne?

Je ne demande rien sur la raison pour laquelle la portée privée du package est par défaut, je demande quand elle doit être utilisée indépendamment des valeurs par défaut. La plupart des discussions sur la raison pour laquelle il s'agit de la valeur par défaut n'interviennent pas vraiment lorsque la portée du package est réellement utile, plaidant simplement pour la raison pour laquelle les deux autres étendues couramment utilisées ne devraient pas être par défaut afin que le package gagne par processus d'élimination. De plus, ma question porte sur l' état actuel du développement. Plus précisément, avons-nous développé au point où d'autres outils et paradigmes rendent la portée du package moins utile qu'elle ne l'était lorsque la décision de la rendre par défaut était logique.

dsollen
la source
Expérience de réflexion: à quoi ressembleraient les programmes Java si vous n'aviez pas de packages? Il se peut que vous n'ayez pas regardé ou travaillé sur de gros programmes Java.
Robert Harvey
Regardez le code pour java.util.Stringet java.util.Integer.
2
La réponse la plus votée sur la question en double couvre l'utilisation la plus importante de package-private, répondant implicitement à la raison pour laquelle il est utile.
2
Je ne suis pas du tout convaincu par le doublon. Cette question demande "À quoi sert la portée du package?" tandis que l'autre demande (entre autres) "Étant donné que la portée du package est la valeur par défaut, à quoi sert la portée privée?" De plus, la réponse acceptée sur la dupe et la réponse acceptée ici font des points complètement différents.
Ixrec

Réponses:

2

Tout au long du java.util.*package, il existe des cas où le code est écrit avec une protection au niveau du package. Par exemple, ce morceau de java.util.String- un constructeur dans Java 6 :

// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

ou cette méthode getChars :

/** Copy characters from this string into dst starting at dstBegin. 
    This method doesn't perform any range checking. */
void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, offset, dst, dstBegin, count);
}

La raison en est que les concepteurs du code (et java.util.*peuvent être considérés comme une bibliothèque assez grande) souhaitaient avoir la possibilité d'avoir des performances plus rapides au prix d'une perte de sécurité variée (contrôles de plage sur les tableaux, accès direct à d'autres champs qui impliquent l'implémentation plutôt que l'interface, un accès «non sécurisé» à des parties de la classe qui sont autrement considérées comme immuables via des méthodes publiques).

En limitant l'accès à ces méthodes et champs aux seules autres classes du java.utilpackage, cela permet un couplage plus étroit entre elles mais évite également d'exposer ces détails d'implémentation au monde.

Si vous travaillez sur une application et n'avez pas à vous soucier des autres utilisateurs, la protection au niveau du package n'est pas quelque chose dont vous devez vous soucier. Outre divers aspects de la pureté du design, vous pouvez tout faire publicet être d'accord avec cela.

Cependant, si vous travaillez sur une bibliothèque qui doit être utilisée par d'autres (ou si vous voulez éviter d'enchevêtrer trop vos classes pour une refactorisation ultérieure), vous devez utiliser le niveau de protection le plus strict qui vous soit accordé pour vos besoins. Cela signifie souvent private. Parfois, cependant, vous devez exposer un peu des détails de l'implémentation à d'autres classes du même package pour éviter de vous répéter ou pour faciliter l'implémentation réelle au détriment du couplage des deux classes et de leurs implémentations. Ainsi, la protection au niveau du package.


la source
1
J'ai regardé java.util et je le vois. Cependant, je remarque également que c'est une GRANDE bibliothèque. De nos jours, il semble que les bibliothèques de grande taille soient rarement écrites sans utiliser de sous-packages; et ce sont ces divisions de sous-ensembles qui conduisent à des limites. Je ne dis pas que java.util est faux, mais il semble qu'ils pourraient s'en tirer avec un gros paquet uniquement parce qu'ils avaient des programmeurs EXTRÊMEMENT bons en qui ils pouvaient avoir confiance pour tout faire correctement avec une encapsulation plutôt limitée dans un paquet; Je me sentirais nue en faisant confiance à autant de potentiel pour faire du mal aux développeurs moyens qui peuvent travailler sur des packages similaires à moi.
dsollen
1
@dsollen Si vous creusez dans Spring et Hibernate ou dans de grandes bibliothèques similaires, vous trouverez des choses similaires. Il y a des moments où vous avez besoin d'un couplage plus étroit. On devrait avoir la possibilité de coder l'examen des soumissions et de s'assurer que tout est correct. Si vous n'avez pas besoin d'un couplage plus étroit ou si vous ne faites pas confiance aux autres codeurs, les champs publics, les packages ou les champs ou méthodes protégés ne sont pas toujours appropriés. Cependant, cela ne signifie pas qu'il n'est pas utile.
en bref, je serais tenté de faire des packages plus restreints et de ne pas m'inquiéter du niveau d'optimisation (qui n'est qu'un gaspillage dans 95% des projets) pour mes besoins quotidiens. Ainsi, ma question de suivi devient: la portée au niveau du package est-elle un potentiel d'optimisation très spécifique utilisé uniquement dans certaines API massives largement utilisées? IE, seuls les grands auront besoin ou voudront assouplir les protections à ce point? Y a-t-il des raisons pour lesquelles de bons programmeurs travaillant sur une API même «standard» avec des bases d'utilisateurs plus petites trouveraient une utilité à cela?
dsollen
Je n'ai pas reçu mon message de suivi assez rapidement :) Je comprends et je vois ce que vous avez dit, je n'en discute pas du tout. Plus simplement poser une question de suivi quant à son utilité pour nous autres bons programmeurs, mais ceux qui ne parviennent pas à travailler sur de si grandes API. Plus précisément, s'agit-il principalement d'une optimisation au détriment du compromis d'encapsulation qui n'est effectuée que lorsque vous avez vraiment besoin de modifier les performances.
dsollen
2
@dsollen il ne s'agit pas de performances (bien que ce soit une raison pour exposer l'implémentation) mais plutôt d'encapsulation. Vous le faites pour éviter de répéter le code Integer.toString()et String.valueOf(int)pouvez partager le code sans l'exposer au monde. Les java.utilclasses doivent travailler de concert pour fournir un ensemble plus large de fonctionnalités plutôt que d'être des classes individuelles qui sont entièrement des îles en elles-mêmes - elles sont ensemble dans un package et partagent la fonctionnalité d'implémentation via la portée au niveau du package.
5

Parfois, j'augmente la visibilité des méthodes privées ou protégées à empaqueter pour permettre à un junit-test d'accéder à certains détails d'implémentation qui ne devraient pas être accessibles de l'extérieur.

comme le test junit a le même package que l'élément sous test, il peut accéder à ces détails d'implémentation

exemple

  public class MyClass {
    public MyClass() {
        this(new MyDependency());
    }

    /* package level to allow junit-tests to insert mock/fake implementations */ 
    MyClass(IDependency someDepenency) {...}
  }

C'est discutable si c'est un "bon" usage ou non mais c'est pragmatique

k3b
la source
2
Je mets toujours des tests dans un package différent, sinon je trouve que j'ai laissé une méthode clé dans la portée par défaut qui ne permet à personne de l'appeler ...
soru
Je ne le fais jamais avec des méthodes, mais c'est une fonctionnalité intéressante à utiliser pour les dépendances injectées. Par exemple, lors du test des EJB, les EntityManagers injectés peuvent être moqués en définissant un niveau EM de package avec un objet Mock où il serait injecté au moment de l'exécution par le serveur d'applications.
jwenting
Pourquoi passer protectedà la portée du package? fournit protected déjà un accès au package. C'est un peu bizarre, mais je pense qu'ils ne voulaient pas exiger des déclarations de portée composées telles que protected packagescope void doSomething()(qui nécessite également un nouveau mot clé).
tgm1024 - Monica a été maltraitée
2

Ce n'est pas du tout utile, surtout par défaut, dans un monde où les gens ont tendance à utiliser les noms en pointillés des packages comme s'ils étaient des sous-packages. Étant donné que Java n'a pas de sous-package, xyinternals n'a donc pas plus accès à xy qu'à ab Les noms de package sont identiques ou différents, une correspondance partielle n'est pas différente de n'avoir rien en commun.

Je suppose que les développeurs originaux ne s'attendaient pas à ce que cette convention soit utilisée, et voulaient garder les choses simples sans ajouter la complexité des packages hiérarchiques de style Ada.

Java 9 répond en quelque sorte à cela, dans la mesure où vous pouvez placer plusieurs packages dans un module et n'en exporter que certains. Mais cela rendra la portée du package encore plus redondante.

Dans un monde idéal, ils changeraient la portée par défaut en «portée du module, s'il existe un module contenant, sinon portée du paquet». Ensuite, vous pouvez l'utiliser pour partager l'implémentation au sein d'un module, permettant le refactoring sans casser les interfaces exportées. Mais peut-être y a-t-il une subtilité pour laquelle cela romprait la compatibilité descendante, ou serait autrement mauvais.

Soru
la source
Je trouve le commentaire java 9 intéressant. Je pense qu'une simple capacité à reconnaître les hérachies de paquets et à définir la portée du paquet par rapport à un paquet parent (en utilisant des annotations?) Serait bien.
dsollen
1
@dsollen, peut-être, mais ma première inclination est que nous commencerions à plaquer les fers à pneus avec celui-ci. Une première étape bien meilleure vers la clarté (qui apporte la sécurité dans une certaine mesure) à mon avis serait d'inventer un mot clé de portée de package réel. Il pourrait même s'agir d'un "package" :)
tgm1024 - Monica a été maltraitée
2

Le type de cohésion que vous décrivez n'est pas vraiment le meilleur exemple de l'endroit où vous utiliseriez l'accès au package. Le package est pratique pour créer des composants, des modules interchangeables sur une interface.

Voici un exemple approximatif d'un scénario. Supposons que votre code ait été réorganisé pour être davantage axé sur la cohésion fonctionnelle et la composante. Peut-être 3 pots comme:

**MissionManager.jar** - an application which uses the Java Service Provider Interface to load some implementation of missions, possible provided by a 3rd party vendor.

**missions-api.jar** - All interfaces, for services and model
    com...missions
        MissionBuilder.java   - has a createMission method
        Mission.java - interface for a mission domain object
    com...missions.strategy
        ... interfaces that attach strategies to missions ...
    com...missions.reports
        .... interfaces todo with mission reports ....

   **MCo-missionizer-5000.jar** -  provides MCo's Missionizer 5000 brand mission implementation.
       com...missions.mco
                  Mission5000.java - implements the Mission interface.
       com...missions.strategy.mco
              ... strategy stuff etc ...

En utilisant le niveau de package dans Mission5000.java, vous pouvez vous assurer qu'aucun autre package ou composant tel que MissionManager ne peut coupler étroitement l'implémentation de la mission. Ce n'est que le composant "tiers" fourni par "M co" qui crée réellement sa propre mise en œuvre de marque spéciale d'une mission. Le gestionnaire de mission doit appeler le MissionBuiler.createMission (...) et utiliser l'interface définie.

De cette façon, si le composant d'implémentation de Mission est remplacé, nous savons que les implémenteurs de MissionManager.jar n'ont pas contourné l'API et utilisent directement l'implémentation de MCo.

Par conséquent, nous pouvons échanger MCo-missionizer5000.jar avec un produit concurrent. (Ou peut-être une maquette pour tester l'unité du gestionnaire de mission)

À l'inverse, MCo peut faire un peu de dissimulation de ses secrets commerciaux de missionnaires propriétaires.

(Ceci est également lié à l'application des idées d' instabilité et d'abstrait dans les composants.)

Chris
la source
-1

Étant donné que la portée du package en java n'est pas conçue de manière hiérarchique, elle n'est guère utile. Nous n'utilisons pas du tout la portée du package et définissons toutes les classes comme publiques.

Au lieu de cela, nous utilisons nos propres règles d'accès basées sur la hiérarchie des packages et les noms de packages prédéfinis.

Si vous êtes intéressé par les détails google pour "CODERU-Rules".

Alexander Rubinov
la source