En Java, pourquoi les membres protégés ont-ils été rendus accessibles aux classes du même package?

14

De la documentation officielle ...

Package de classe de modificateur Monde de sous-classe 
public YYYY 
protégé YYYN 
aucun modificateur YYNN 
YNNN privé 

Le fait est que je ne me souviens pas avoir eu un cas d'utilisation où j'avais besoin d'accéder aux membres protégés d'une classe dans le même package.

Quelles étaient les raisons de cette mise en œuvre?

Edit: Pour clarifier, je cherche un cas d'utilisation spécifique où une sous-classe et une classe dans le même package doivent accéder à un champ ou une méthode protégée.

package some.package;
public class A {
 protected void protectedMethod(){
  // do something
 }
}

package another.package;
public class B extends A{
 public void someMethod(){
  // accessible because B is a subclass of A
  protectedMethod();
 }
} 

package some.package;
public class C {
 public void anotherMethod(){
  // accessible because C is in the same package as A
  protectedMehtod();
 }
}
jramoyo
la source

Réponses:

4

la recherche d'un cas d'utilisation spécifique où une sous-classe et une classe dans le même package doivent accéder à un champ ou une méthode protégée ...

Eh bien pour moi, un tel cas d'utilisation est plutôt général que spécifique, et il découle de mes préférences pour:

  1. Commencez avec un modificateur d'accès aussi strict que possible, en ne recourant à un ou plusieurs plus faibles que plus tard, si cela est jugé nécessaire.
  2. Faites en sorte que les tests unitaires résident dans le même package que le code testé.

D'en haut, je peux commencer à concevoir pour mes objets avec des modificateurs d'accès par défaut (je commencerais par privatemais cela compliquerait les tests unitaires):

public class Example {
    public static void main(String [] args) {
        new UnitTest().testDoSomething(new Unit1(), new Unit2());
    }

    static class Unit1 {
        void doSomething() {} // default access
    }
    static class Unit2 {
        void doSomething() {} // default access
    }

    static class UnitTest {
        void testDoSomething(Unit1 unit1, Unit2 unit2) {
            unit1.doSomething();
            unit2.doSomething();
        }
    }
}

Note latérale dans l'extrait ci-dessus Unit1, Unit2et UnitTestsont imbriqués dans Exampleun souci de simplicité de présentation, mais dans un projet réel, j'aurais probablement ces classes dans des fichiers séparés (et UnitTestmême dans un répertoire séparé ).

Ensuite, lorsqu'une nécessité survient, j'affaiblirais le contrôle d'accès par défaut à protected:

public class ExampleEvolved {
    public static void main(String [] args) {
        new UnitTest().testDoSomething(new Unit1(), new Unit2());
    }

    static class Unit1 {
        protected void doSomething() {} // made protected
    }
    static class Unit2 {
        protected void doSomething() {} // made protected
    }

    static class UnitTest {
        // ---> no changes needed although UnitTest doesn't subclass
        // ...and, hey, if I'd have to subclass... which one of Unit1, Unit2?
        void testDoSomething(Unit1 unit1, Unit2 unit2) {
            unit1.doSomething();
            unit2.doSomething();
        }
    }
}

Vous voyez, je peux garder le code de test unitaire ExampleEvolvedinchangé car les méthodes protégées sont accessibles à partir du même package, même si l'accès à l'objet n'est pas une sous-classe .

Moins de changements nécessaires => modification plus sûre; après tout, je n'ai changé que les modificateurs d'accès et je n'ai pas modifié les méthodes Unit1.doSomething()et les Unit2.doSomething()actions, il est donc naturel de s'attendre à ce que le code de test unitaire continue de s'exécuter sans modifications.

moucheron
la source
5

Je dirais que cela comprend deux parties:

  1. L'accès par défaut, "package", est utile dans un large éventail de cas, car les classes ne sont pas toujours une bonne unité d'encapsulation. Divers objets composites où certains objets agissent comme une collection d'autres objets, mais les éléments ne doivent pas être publiquement modifiables, car il existe des invariants dans toute la collection, de sorte que la collection doit avoir un accès élevé aux éléments. C ++ a des amis, Java a accès aux packages.
  2. Maintenant, la portée d'accès "package" est fondamentalement indépendante de la portée "subclass" (protégée). Vous auriez donc besoin de spécificateurs d'accès supplémentaires pour les packages uniquement, les sous-classes uniquement et les packages et sous-classes. La portée "package" est plus restreinte car un ensemble de classes dans un package est généralement défini alors qu'une sous-classe peut apparaître n'importe où. Donc, pour garder les choses simples, Java inclut simplement l'accès au package dans l'accès protégé et n'a pas de spécificateur supplémentaire pour les sous-classes mais pas le package. Bien que vous devriez presque toujours penser protectedà cela exactement.
Jan Hudec
la source
Ne serait-ce pas plus simple si la protectedsous-classe est uniquement? Honnêtement, pendant longtemps, j'avais l'impression que
c'était
@jramoyo: Non, car vous auriez encore besoin de rendre le comportement combiné disponible d'une manière ou d'une autre, ce qui signifierait un autre spécificateur.
Jan Hudec
6
@jramoyo - En C #, protectedest classe et sous - classe uniquement, et internalest une bibliothèque / package à l' échelle. Il a également protected internall'équivalent de Java protected.
Bobson
@Bobson - merci, l'implémentation C # semble être un meilleur choix
jramoyo
5

À mon humble avis, c'était une mauvaise décision de conception en Java.

Je ne fais que spéculer, mais je pense qu'ils voulaient que les niveaux d'accès soient dans une stricte progression: privé - "package" - protégé - public. Ils ne voulaient pas avoir une hiérarchie, où certains champs seraient disponibles pour le package mais pas les sous-classes, certains disponibles pour les sous-classes mais pas le package, et certains les deux.

Pourtant, à mon humble avis, ils auraient dû aller dans l'autre sens: a déclaré que protégé est visible uniquement pour la classe et les sous-classes, et que le package est visible pour la classe, les sous-classes et le package.

J'ai souvent des données dans une classe qui doivent être accessibles aux sous-classes, mais pas au reste du package. J'ai du mal à penser à un cas où l'inverse était vrai. J'ai eu quelques rares occasions où j'ai eu un ensemble de classes interdépendantes qui ont besoin de partager des données mais qui devraient garder ces données à l'abri de l'extérieur de l'ensemble. Alors d'accord, mettez-les dans un paquet, etc. Mais je n'ai jamais eu de cas où je veux que le paquet partage des données, mais je veux le garder des sous-classes. D'accord, je peux imaginer la situation à venir. Je suppose que si ce paquet fait partie d'une bibliothèque qui pourrait être étendue par des classes, je ne sais rien, pour les mêmes raisons que je rendrais les données d'une classe privées. Mais il est beaucoup plus courant de vouloir rendre les données disponibles uniquement pour la classe et ses enfants.

Il y a beaucoup de choses que je garde secrètes entre moi et mes enfants et nous ne partageons pas avec les voisins. Il y a très peu de choses que je garde privées entre moi et les voisins et que je ne partage pas avec mes enfants. :-)

Geai
la source
0

Un bon exemple qui vient immédiatement à l'esprit est les classes d'utilitaires qui sont beaucoup utilisées dans le package, mais vous ne voulez pas d'accès public (les coulisses-chargement d'image à partir du disque, les classes de création / destruction de descripteurs, etc. .) au lieu de tout faire [l'équivalent de Java friend access modifier or idiomdans C++] de toutes les autres classes, tout est automatiquement disponible.

Casey
la source
1
Les classes utilitaires sont davantage un exemple de classes qui sont internes dans leur ensemble plutôt que leurs membres.
Jan Hudec
0

Les cas d'utilisation du modificateur d'accès protégé / package sont similaires à ceux des modificateurs d'accès ami en C ++.

Un cas d'utilisation est lors de l'implémentation du modèle Memento .

L'objet memento doit accéder à l'état interne d'un objet, le conserver, afin de servir de point de contrôle pour les opérations d'annulation.

Déclarer la classe dans le même package est l' un des moyens possibles pour réaliser le modèle Memento, car Java n'a pas de modificateur d'accès "ami" .

Tulains Córdova
la source
1
Non, l'objet memento n'a pas besoin et ne doit pas avoir accès à l'objet. C'est l'objet qui se sérialise dans le souvenir et se désérialise à nouveau. Et le souvenir lui-même est juste un sac de propriété stupide. Aucun des deux ne devrait avoir plus que l'accès public à l'autre.
Jan Hudec
@JanHudec J'ai déclaré littéralement "est -> l'un <- des moyens possibles pour réaliser le modèle Memento" .
Tulains Córdova
Et encore une autre façon possible de réaliser le modèle Memento est de tout rendre public. Autrement dit, je ne vois pas votre point.
Thomas Eding
-1

symétrie?

Il est rare d'avoir besoin d'un tel accès, c'est pourquoi l'accès par défaut est si rarement utilisé. Mais parfois, les frameworks le souhaitent pour le code généré, où les classes wrapper sont placées dans le même package qui interagissent directement avec vos classes, allant aux membres plutôt qu'aux accesseurs pour des raisons de performances. Pensez GWT.

jwenting
la source
1
merci, avez-vous un exemple? sonne comme cela peut être accompli par un accesseur "par défaut" plutôt que "protégé"
jramoyo
-1

La hiérarchie d'encapsulation de Java est bien définie:

Classe -> Package -> Héritage

"Protégé" est juste une forme de confidentialité plus faible que le package-default comme décidé par les concepteurs Java. L'accès aux éléments par défaut du package est limité à un sous-ensemble des entités autorisées à accéder aux éléments protégés.

D'un point de vue mathématique et d'implémentation, il est très logique d'avoir vos ensembles d'entités autorisés à accéder aux éléments à imbriquer. (Vous ne pouviez pas imbriquer votre ensemble d'accès au package dans votre ensemble d'accès protégé car les classes sont autorisées à hériter d'autres packages).

D'un point de vue conceptuel, il est logique d'avoir quelque chose dans java.util pour être "plus convivial" avec une autre classe dans le package que quelque chose dans com.example.foo.bar qui le sous-classe. Dans le premier cas, les cours sont susceptibles d'être écrits par le même auteur, ou au moins des codeurs de la même organisation.

Rich Smith
la source
1
qu'est-ce que cela signifie "juste une forme plus faible ..." ?
gnat