Czy zakres poziomu pakietu Java jest przydatny?


11

Rozumiem ideę zakresu pakietu, a czasami nawet myślałem, że tego chcę. Jednak za każdym razem, gdy zdecydowałem się na poważny zamiar wypróbowania go, odkryłem, że nie odpowiada on potrzebom, które, jak sądzę, będą służyć.

Moim głównym problemem zawsze wydaje się to, że rzeczy, które chciałbym ograniczyć zakres, nigdy nie są w tym samym pakiecie. Można je wszystkie koncepcyjnie połączyć, ale logiczny podział danych w aplikacji ma je jako osobne pakiety podrzędne większego pakietu.

Na przykład mogę mieć model Misji i chcę, aby tylko inne narzędzia Misji, takie jak moje usługi misji, korzystały z niektórych metod. Jednak kończę na Missions.models i Missions.services jako moich pakietach, więc MissionModel i MissionService nie są tym samym zakresem pakietu. Nigdy nie wydaje się, że istnieje sytuacja, w której pakiety odpowiednio zawierają rzeczy, które chciałbym mieć podwyższone uprawnienia bez uwzględnienia wielu rzeczy, których nie chcę mieć; i rzadko kiedy czuję, że przewaga zakresu pakietu w metodzie uzasadnia modyfikację architektury mojego projektu, aby umieścić wszystko w tym samym pakiecie. Często albo Aspekty, albo coś w rodzaju odwrócenia kontroli okazuje się lepszym podejściem do każdego problemu, dla którego krótko rozważałem określenie zakresu.

Jestem ciekawy, że jest to ogólnie uważane za prawdziwe we wszystkich programistach Java, lub jest po prostu fartem mojej pracy. Czy zakres pakietów jest często wykorzystywany w świecie rzeczywistym? Czy istnieje wiele przypadków, w których uważa się, że jest to dobra forma do użycia, czy też jest postrzegana głównie jako starsze zachowanie, które rzadko jest wykorzystywane w nowoczesnym rozwoju?

Nie pytam o to, dlaczego zakres prywatny pakietu jest domyślny, pytam, kiedy należy go używać niezależnie od wartości domyślnych. Większość dyskusji na temat tego, dlaczego jest ona domyślna, tak naprawdę nie wchodzi w grę, gdy zakres pakietu jest rzeczywiście przydatny, zamiast tego argumentując po prostu, dlaczego dwa pozostałe powszechnie używane zakresy nie powinny być domyślne, więc pakiet wygrywa przez proces eliminacji. Ponadto moje pytanie dotyczy obecnego stanu rozwoju. W szczególności opracowaliśmy do tego stopnia, że ​​inne narzędzia i paradygmaty sprawiają, że zakres pakietu jest mniej przydatny niż wtedy, gdy decyzja o nadaniu mu domyślności ma sens.


Eksperyment myślowy: jak wyglądałyby programy Java, gdybyś nie miał pakietów? Możliwe, że nie oglądałeś ani nie pracowałeś nad żadnymi dużymi programami Java.
Robert Harvey

Spójrz na kod dla java.util.Stringi java.util.Integer.


2
Najczęściej głosowana odpowiedź na zduplikowane pytanie obejmuje najważniejsze zastosowanie pakietu prywatnego, domyślnie odpowiadając, dlaczego jest to użyteczne.

2
Duplikat wcale mnie nie przekonuje. To pytanie brzmi: „Do czego służy zakres pakietu?” podczas gdy drugi pyta (między innymi) „Biorąc pod uwagę zakres pakietu jest domyślny, po co jest zakres prywatny?” Plus zaakceptowana odpowiedź na duplikacie i tutaj zaakceptowana odpowiedź robią zupełnie inne punkty.
Ixrec

Odpowiedzi:


2

W całym java.util.*pakiecie występują przypadki, w których kod jest zapisywany z ochroną na poziomie pakietu. Na przykład ten bit java.util.String- konstruktor w Javie 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;
}

lub ta metoda 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);
}

Powodem tego jest to, że projektanci kodu (i java.util.*można go uważać za raczej dużą bibliotekę) chcieli mieć możliwość szybszej wydajności kosztem utraty różnych bezpieczeństwa (kontrola zasięgu tablic, bezpośredni dostęp do innych pól implikuje raczej implementację niż interfejs, „niebezpieczny” dostęp do części klasy, które w innym przypadku są uznawane za niezmienne za pomocą metod publicznych).

Ograniczając dostęp do tych metod i pól przez inne klasy w java.utilpakiecie, umożliwia to ściślejsze powiązanie między nimi, ale także pozwala uniknąć ujawnienia tych szczegółów implementacji światu.

Jeśli pracujesz nad aplikacją i nie musisz martwić się o innych użytkowników, ochrona na poziomie pakietu nie jest czymś, o co musisz się martwić. Oprócz różnych aspektów czystości projektu, możesz zrobić wszystko publici być z tym w porządku.

Jeśli jednak pracujesz nad biblioteką, która ma być używana przez innych (lub chcesz uniknąć zbytniego zaplątania się klas w celu późniejszego refaktoryzacji), powinieneś zastosować najściślejszy poziom ochrony zapewniony dla twoich potrzeb. Często oznacza to private. Czasami jednak trzeba udostępnić trochę szczegółów implementacji innym klasom w tym samym pakiecie, aby uniknąć powtarzania się lub aby ułatwić faktyczną implementację kosztem połączenia dwóch klas i ich implementacji. Zatem ochrona na poziomie pakietu.


1
Przejrzałem java.util i widzę to. Jednak zauważam również, że jest to DUŻA biblioteka. Obecnie wydaje się, że duże biblioteki rzadko są pisane bez użycia podpakietów; i te podziały paczek prowadzą do ograniczeń. Nie twierdzę, że java.util się myli, ale wygląda na to, że mogliby uciec z dużym pakietem tylko dlatego, że mieli BARDZO dobrych programistów, którym mogliby zaufać, że zrobili wszystko dobrze z raczej ograniczoną enkapsulacją w pakiecie; Czułbym się nagi, ufając takiemu potencjałowi wyrządzenia krzywdy przeciętnym programistom, którzy mogą pracować na podobnych pakietach jak ja.
dsollen,

1
@dsollen Jeśli zagłębisz się w Spring i Hibernacja lub podobne duże biblioteki, znajdziesz podobne rzeczy. Są chwile, kiedy potrzebujesz bliższego sprzęgła. One powinny mieć możliwość przejrzenia kodu przesłanie i upewnić, że wszystko jest prawidłowe. Jeśli nie potrzebujesz bliższego łączenia lub w ogóle nie ufasz innym koderom, wówczas pola publiczne, pakiety lub pola chronione lub metody nie zawsze są odpowiednie. Nie oznacza to jednak, że nie jest to przydatne.

w skrócie, chciałbym stworzyć bardziej ograniczone pakiety i nie martwić się poziomem optymalizacji (który jest tylko marnotrawstwem w 95% projektów) dla moich codziennych potrzeb. Tak więc moje dalsze pytanie brzmi: czy zakres na poziomie pakietu jest bardzo specyficznym potencjałem optymalizacji wykorzystywanym tylko w niektórych szeroko wykorzystywanych, masywnych interfejsach API? Czy tylko wielcy będą potrzebować lub chcą rozluźnić zabezpieczenia do tego stopnia? Czy istnieją powody, dla których dobrzy programiści pracujący na nawet „standardowym” interfejsie API z mniejszymi bazami użytkowników mogliby znaleźć zastosowanie?
dsollen,

Nie dostałem wystarczająco szybko mojego następnego postu :) Rozumiem i rozumiem, o czym mówiłeś, w ogóle go nie debatuję. Więcej po prostu zadając kolejne pytanie dotyczące przydatności dla reszty z nas dobrych programistów, ale takich, którzy nie pracują na tak ogromnych interfejsach API. W szczególności jest to głównie optymalizacja kosztem enkapsulacji, która jest dokonywana tylko wtedy, gdy naprawdę trzeba poprawić wydajność.
dsollen

2
@dsollen nie chodzi o wydajność (choć jest to jeden z powodów ujawnienia implementacji), ale raczej enkapsulację. Robisz to, aby uniknąć powtarzania kodu Integer.toString()i String.valueOf(int)możesz udostępniać kod bez ujawniania go światu. Te java.utilzajęcia są do pracy w porozumieniu w celu zapewnienia większej całości funkcjonalności zamiast być zajęcia indywidualne, które są całkowicie wyspy sami dla siebie - są ze sobą w realizacji funkcjonalności poprzez pakiet poziom scopingu opakowania oraz zakładowego.

5

Czasami zwiększam widoczność prywatnych lub chronionych metod do pakowania, aby umożliwić testowi junit dostęp do niektórych szczegółów implementacji, do których dostęp nie powinien być uzyskiwany z zewnątrz.

ponieważ test junit ma taki sam pakiet jak testowany element, może uzyskać dostęp do tych szczegółów implementacji

przykład

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

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

Można dyskutować, czy jest to „dobre” użycie, czy nie, ale jest pragmatyczne


2
Zawsze umieszczam testy w innym pakiecie, w przeciwnym razie okazuje się, że pozostawiłem kluczową metodę w domyślnym zakresie, która nie pozwala nikomu nazywać się ...
soru

Nigdy nie robię tego metodami, ale jest to miła funkcja do użycia w przypadku wstrzykiwanych zależności. Na przykład podczas testowania komponentów EJB wstrzykiwany menedżer EntityManagers można wyśmiewać, ustawiając EM na poziomie pakietu za pomocą obiektu próbnego, w którym w czasie wykonywania zostanie wstrzyknięty przez serwer aplikacji.
jwenting

Dlaczego warto protectedprzejść na zakres pakietu? protected już zapewnia dostęp do paczki. To trochę dziwne dziwactwo, ale myślę, że nie chcieli wymagać deklaracji o złożonym zakresie, takich jak protected packagescope void doSomething()(co wymaga także nowego słowa kluczowego).
tgm1024 - Monica została źle traktowana

2

Nie jest to szczególnie przydatne, zwłaszcza domyślnie, w świecie, w którym ludzie używają kropkowanych nazw paczek, tak jakby to były paczki. Ponieważ Java nie ma czegoś takiego jak pod-pakiet, więc xyinternals nie ma więcej dostępu do xy niż do ab Nazwy pakietów są takie same lub różne, częściowe dopasowanie nie różni się niczym od braku wspólnego.

Sądzę, że pierwotni programiści nigdy nie spodziewali się zastosowania tej konwencji i chcieli uprościć sprawę bez dodawania złożoności pakietów hierarchicznych w stylu Ada.

Java 9 w pewnym sensie rozwiązuje ten problem, ponieważ można umieścić kilka pakietów w module i wyeksportować tylko niektóre. Ale to sprawi, że zakres pakietu będzie jeszcze bardziej zbędny.

W idealnym świecie zmieniliby domyślny zakres na „zakres modułu, jeśli istnieje moduł zawierający, w przeciwnym razie zakres pakietu”. Następnie możesz użyć go do współdzielenia implementacji w module, umożliwiając refaktoryzację bez zerwania eksportowanych interfejsów. Ale może jest jakaś subtelność, dlaczego miałoby to zerwać z kompatybilnością wsteczną lub być w inny sposób złe.


Uważam, że komentarz do Java 9 jest interesujący. Myślę, że prosta byłaby umiejętność rozpoznawania herachies pakietów i jakoś definiowania zakresu pakietu względem niektórych pakietów nadrzędnych (używając adnotacji?).
dsollen

1
@dollen, być może, ale moja pierwsza skłonność polega na tym, że zaczęlibyśmy na nim nakładać opony na żelazka. Zdecydowanie lepszym pierwszym krokiem do przejrzystości (który w pewnym stopniu zwiększa bezpieczeństwo) byłoby wymyślenie rzeczywistego słowa kluczowego zakresu pakietu. Może to być nawet „pakiet” :)
tgm1024 - Monica została źle traktowana

2

Rodzaj spójności swój opisującym tak naprawdę nie jest najlepszym przykładem na to, gdzie należy użyć dostępu do opakowania. Pakiet jest przydatny do tworzenia komponentów, modułów, które można zamieniać w interfejsie.

Oto przybliżony przykład scenariusza. Załóżmy, że Twój kod został zreorganizowany, aby bardziej skupiał się na spójności funkcjonalnej i komponentowaniu. Być może 3 słoiki, takie jak:

**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 ...

Korzystając z poziomu pakietu w Mission5000.java, możesz nie mieć pewności, że żaden inny pakiet lub komponent, taki jak MissionManager, nie może ściśle powiązać z realizacją misji. To tylko element dostarczony przez „firmę trzecią” z „M co” faktycznie tworzy tam specjalną markową implementację Misji. Menedżer misji musi wywołać MissionBuiler.createMission (...) i użyć zdefiniowanego interfejsu.

W ten sposób, jeśli składnik implementacji Misji zostanie zastąpiony, wiemy, że implementatorzy pliku MissionManager.jar nie ominęli interfejsu API i nie używają bezpośrednio implementacji MCo.

Stąd możemy zamienić MCo-missionizer5000.jar na konkurencyjny produkt. (A może próbka do testowania jednostek przez Kierownika misji)

I odwrotnie, MCo potrafi ukryć swoje tajemnice handlowe misjonarzy.

(Jest to związane z egzekwowaniem pomysłów dotyczących niestabilności i abstrakcyjności również w komponentach.)


-1

Ponieważ zakres pakietu w Javie nie jest zaprojektowany w sposób hierarchiczny, jest to mało użyteczne. W ogóle nie korzystamy z zakresu pakietu i definiujemy wszystkie klasy jako publiczne.

Zamiast tego używamy własnych reguł dostępu opartych na hierarchii pakietów i wstępnie zdefiniowanych nazwach pakietów.

Jeśli interesują Cię szczegóły, przejdź do Google dla „Reguł CODERU”.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.