Dlaczego w Javie chronieni członkowie byli dostępni dla klas tego samego pakietu?

14

Z oficjalnej dokumentacji ...

Pakiet klasy modyfikatora Podklasa Świat 
publiczne RRRR 
chroniony YYYN 
bez modyfikatora YYNN 
prywatny YNNN 

Chodzi o to, że nie pamiętam przypadku użycia, w którym potrzebowałem uzyskać dostęp do chronionych członków z klasy w tym samym pakiecie.

Jakie były przyczyny tego wdrożenia?

Edycja: Aby to wyjaśnić, szukam konkretnego przypadku użycia, w którym zarówno podklasa, jak i klasa w tym samym pakiecie muszą uzyskać dostęp do chronionego pola lub metody.

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
źródło

Odpowiedzi:

4

szukasz konkretnego przypadku użycia, w którym zarówno podklasa, jak i klasa w tym samym pakiecie muszą uzyskać dostęp do chronionego pola lub metody ...

Cóż, dla mnie taki przypadek użycia jest raczej ogólny niż konkretny i wynika z moich preferencji:

  1. Zacznij od możliwie jak najściślejszego modyfikatora dostępu, uciekając się do słabszego (-ych) dopiero później, gdy uzna to za konieczne.
  2. Testy jednostkowe powinny znajdować się w tym samym pakiecie co testowany kod.

Z góry mogę rozpocząć projektowanie dla moich obiektów z domyślnymi modyfikatorami dostępu (zacznę od, privateale skomplikowałoby to testowanie jednostek):

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();
        }
    }
}

Dygresja w powyższym fragmencie, Unit1, Unit2i UnitTestzagnieżdżone wewnątrz Exampledla uproszczenia prezentacji, ale w rzeczywistym projekcie, będę prawdopodobnie mieć tych klas w osobnych plikach (a UnitTestnawet w osobnym katalogu ).

Następnie, gdy zajdzie taka potrzeba, osłabiłbym kontrolę dostępu z domyślnych do 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();
        }
    }
}

Widzisz, mogę zachować kod testu jednostkowego w ExampleEvolvedniezmienionej postaci, ponieważ chronione metody są dostępne z tego samego pakietu, nawet jeśli dostęp do obiektu nie jest podklasą .

Potrzebne mniej zmian => bezpieczniejsza modyfikacja; w końcu zmieniłem tylko modyfikatory dostępu i nie modyfikowałem metod Unit1.doSomething()i działań Unit2.doSomething(), więc naturalne jest oczekiwanie, że kod testu jednostkowego będzie kontynuowany bez modyfikacji.

komar
źródło
5

Powiedziałbym, że składa się z dwóch części:

  1. Domyślny dostęp „pakietowy” jest przydatny w szerokim zakresie przypadków, ponieważ klasy nie zawsze są dobrą jednostką enkapsulacji. Różne obiekty złożone, w których jeden obiekt działa jako zbiór innych obiektów, ale elementów nie można publicznie modyfikować, ponieważ istnieją pewne niezmienniki w całej kolekcji, więc kolekcja musi mieć wyższy dostęp do elementów. C ++ ma przyjaciół, Java ma dostęp do pakietów.
  2. Teraz zakres dostępu do „pakietu” jest w zasadzie niezależny od zakresu (chronionego) „podklasy”. Potrzebne będą więc dodatkowe specyfikatory dostępu tylko dla pakietu, tylko dla podklas oraz dla pakietu i podklas. Zakres „pakietu” jest bardziej ograniczony, ponieważ zestaw klas w pakiecie jest zwykle określony, podczas gdy podklasa może pojawiać się w dowolnym miejscu. Aby więc uprościć sprawę, Java po prostu obejmuje dostęp do pakietu w chronionym dostępie i nie ma dodatkowego specyfikatora dla podklas, ale nie dla pakietu. Chociaż prawie zawsze powinieneś myśleć o protectedtym dokładnie.
Jan Hudec
źródło
Czy nie byłoby łatwiej, gdyby protectedbyła tylko podklasa? Szczerze mówiąc, przez długi czas miałem wrażenie, że takie jest zachowanie
jramoyo
@jramoyo: Nie, ponieważ nadal musiałbyś w jakiś sposób udostępnić połączone zachowanie, co oznaczałoby inny specyfikator.
Jan Hudec
6
@jramoyo - w języku C # protectedjest tylko klasą i podklasą i internalobejmuje bibliotekę / pakiet. Ma także protected internalodpowiednik Javy protected.
Bobson
@ Bobson - dzięki, implementacja w C # wydaje się lepszym wyborem
jramoyo,
5

IMHO, to była zła decyzja projektowa w Javie.

Po prostu spekuluję, ale myślę, że chcieli, aby poziomy dostępu były ściśle określone: ​​prywatny - „pakiet” - chroniony - publiczny. Nie chcieli mieć hierarchii, w której niektóre pola byłyby dostępne dla pakietu, ale nie dla podklas, niektóre dla podklas, ale nie dla pakietu, a niektóre oba.

Mimo to, moim skromnym zdaniem, powinni byli pójść w drugą stronę: Powiedzieli, że chroniony jest widoczny tylko dla klasy i podklas, a ten pakiet jest widoczny dla klasy, podklas i pakietu.

Często mam dane w klasie, które muszą być dostępne dla podklas, ale nie dla reszty pakietu. Trudno mi wymyślić przypadek, w którym sytuacja była odwrotna. Miałem kilka rzadkich okazji, w których miałem pakiet powiązanych ze sobą klas, które muszą dzielić się danymi, ale które powinny chronić te dane przed spakowaniem. W porządku, umieść je w pakiecie itp. Ale nigdy nie miałem przypadku, w którym chciałbym, aby pakiet współdzielił dane, ale chcę, aby nie był przechowywany w podklasach. Okej, mogę sobie wyobrazić nadchodzącą sytuację. Podejrzewam, że jeśli ten pakiet jest częścią biblioteki, która może być rozszerzona o klasy, o których nic nie wiem, z tych samych powodów, dla których uczynię dane w klasie prywatnymi. Ale o wiele bardziej powszechne jest udostępnianie danych tylko klasie i jej dzieciom.

Jest wiele rzeczy, które zachowuję prywatność między mną a moimi dziećmi i nie dzielimy się z sąsiadami. Jest bardzo niewiele rzeczy, które trzymam w tajemnicy między mną a sąsiadami i nie dzielę się nimi z moimi dziećmi. :-)

Sójka
źródło
0

Dobrym przykładem, który natychmiast przychodzi mi na myśl, są klasy narzędzi, które są często używane w pakiecie, ale nie chcesz publicznego dostępu (za kulisami ładowanie obrazu z dysku, klasy tworzenia uchwytów / niszczenia itp. .) zamiast tworzenia wszystko [odpowiednik Java friend access modifier or idiomw C++] każdej innej klasy wszystko jest automatycznie dostępna.

Casey
źródło
1
Klasy użyteczności są raczej przykładem klas, które są wewnętrzne jako całość niż ich członków.
Jan Hudec
0

Przypadki użycia dla modyfikatora dostępu chronionego / dostępu do pakietu są podobne do przypadków dla modyfikatorów dostępu znajomych w C ++.

Jednym z przypadków użycia jest implementacja wzorca Memento .

Obiekt memento musi uzyskać dostęp do wewnętrznego stanu obiektu, aby go zatrzymać, aby służyć jako punkt kontrolny dla operacji cofania.

Deklarowanie klasy w tym samym pakiecie jest jednym z możliwych sposobów osiągnięcia wzorca Memento, ponieważ Java nie ma modyfikatora dostępu „zaprzyjaźnionego” .

Tulains Córdova
źródło
1
Nie, obiekt pamiątkowy nie potrzebuje i nie powinien mieć dostępu do obiektu. Jest to obiekt, który serializuje się w pamiątkę i ponownie szereguje. A sama pamiątka to tylko głupia torba. Żadne z nich nie powinno mieć więcej niż publiczny dostęp do drugiego.
Jan Hudec
@ JanHudec Stwierdziłem dosłownie, że „jest -> jednym <- z możliwych sposobów osiągnięcia wzorca Memento” .
Tulains Córdova
A jeszcze innym możliwym sposobem na osiągnięcie wzoru Memento jest upublicznienie wszystkiego. To znaczy, nie rozumiem twojego punktu widzenia.
Thomas Eding,
-1

symetria?

Rzadko potrzebny jest taki dostęp, dlatego dostęp domyślny jest tak rzadko używany. Ale czasami frameworki chcą tego dla wygenerowanego kodu, w którym klasy otoki są umieszczane w tym samym pakiecie, który oddziałuje bezpośrednio z twoimi klasami, przechodząc do członków zamiast do akcesorów ze względów wydajnościowych. Pomyśl GWT.

jwenting
źródło
1
dzięki, masz przykład? wygląda na to, że można to osiągnąć za pomocą „domyślnego” akcesorium zamiast „chronionego”
jramoyo
-1

Hierarchia hermetyzacji Java jest dobrze zdefiniowana:

Klasa -> Pakiet -> Dziedziczenie

„Protected” jest po prostu słabszą formą prywatności niż domyślny pakiet, zgodnie z decyzją projektantów Java. Dostęp do domyślnych elementów pakietu jest ograniczony do podzbioru podmiotów, które mają dostęp do elementów chronionych.

Z punktu widzenia matematyki i implementacji sensowne jest posiadanie zestawów jednostek, które mają dostęp do zagnieżdżanych elementów. (Nie można zagnieździć zestawu dostępu do pakietu w zestawie chronionego dostępu, ponieważ klasy mogą dziedziczyć z innych pakietów).

Z koncepcyjnego punktu widzenia sensownie jest mieć coś w java.util, aby być „bardziej przyjaznym” z inną klasą w pakiecie niż coś w com.example.foo.bar, który to podklasuje. W pierwszym przypadku klasy prawdopodobnie zostaną napisane przez tego samego autora lub co najmniej programistów z tej samej organizacji.

Rich Smith
źródło
1
co to znaczy „tylko słabsza forma ...” ?
komara