Java: widoczność podpakietu?

150

Mam w projekcie dwa pakiety: odp.proji odp.proj.test. Są pewne metody, które chcę, aby były widoczne tylko dla klas w tych dwóch pakietach. W jaki sposób mogę to zrobić?

EDYCJA: Jeśli w Javie nie ma koncepcji podpakietu, czy można to obejść? Mam pewne metody, które chcę, aby były dostępne tylko dla testerów i innych członków tego pakietu. Czy powinienem po prostu wrzucić wszystko do tego samego opakowania? Zastosuj obszerną refleksję?

Nick Heiner
źródło
2
Nawiasem mówiąc, testy powinny zawsze sprawdzać zachowanie twoich obiektów jako obserwowalne spoza pakietu. Dostęp do metod / klas z zakresu pakietu z twoich testów mówi mi, że testy prawdopodobnie testują implementacje, a nie zachowania. Używając narzędzi do budowania, takich jak maven lub gradle, ułatwią one uruchamianie testów w tej samej ścieżce klas, ale nie będą uwzględnione w ostatecznym pliku jar (dobrze), więc nie ma potrzeby, aby miały różne nazwy pakietów. Jednak umieszczenie ich w oddzielnych pakietach ma na celu wymuszenie, że nie masz dostępu do zakresu prywatnego / domyślnego, a tym samym testowanie tylko publicznego interfejsu API.
derekv
3
Może to być prawdą, jeśli pracujesz w sposób czysto oparty na zachowaniu i chcesz, aby twoje testy przeprowadzały tylko testy czarnoskrzynkowe. Mogą jednak wystąpić przypadki, w których realizacja pożądanego zachowania wymaga nieuchronnie dużej złożoności cyklicznej. W takim przypadku dobrze byłoby rozbić implementację na mniejsze, prostsze fragmenty (nadal prywatne dla implementacji) i napisać kilka testów jednostkowych, aby wykonać testy białej skrzynki na różnych ścieżkach przez te fragmenty.
James Woods

Odpowiedzi:

165

Nie możesz. W Javie nie ma pojęcia o podpakiecie, tak odp.proji odp.proj.testsą całkowicie oddzielne pakiety.

starblue
źródło
10
Chociaż podoba mi się to w ten sposób, to mylące, że większość IDE łączy razem pakiety o tej samej nazwie. Dzięki za wyjaśnienie.
JacksOnF1re
Nie jest to do końca dokładne: JLS definiuje podpakiety, chociaż jedyne znaczenie językowe, jakie mają, to zakazanie „pakietu zawierającego podpakiet o tej samej nazwie prostej co typ najwyższego poziomu”. Właśnie dodałem odpowiedź na to pytanie, szczegółowo to wyjaśniając.
M. Justin
59

Nazwy twoich pakietów wskazują, że aplikacja tutaj służy do testowania jednostkowego. Typowym wzorcem jest umieszczenie klas, które chcesz przetestować, i kodu testu jednostkowego w tym samym pakiecie (w twoim przypadku odp.proj), ale w różnych drzewach źródłowych. Więc umieściłbyś swoje klasy src/odp/proji kod testowy w test/odp/proj.

Java ma modyfikator dostępu „pakiet”, który jest domyślnym modyfikatorem dostępu, gdy żaden nie jest określony (tj. Nie określono publicznego, prywatnego lub chronionego). Z modyfikatorem dostępu „pakiet” tylko klasy w odp.projbędą miały dostęp do metod. Należy jednak pamiętać, że w Javie nie można polegać na modyfikatorach dostępu do wymuszania reguł dostępu, ponieważ po odbiciu możliwy jest każdy dostęp. Modyfikatory dostępu są jedynie sugestywne (chyba że obecny jest restrykcyjny menedżer bezpieczeństwa).

Asaf
źródło
11

To nie jest żaden szczególny związek między odp.proji odp.proj.test- tak się składa, że nazywa się je pozornie spokrewnionymi.

Jeśli pakiet odp.proj.test po prostu udostępnia testy, możesz użyć tej samej nazwy pakietu ( odp.proj). IDE, takie jak Eclipse i Netbeans, utworzą oddzielne foldery ( src/main/java/odp/proji src/test/java/odp/proj) z tą samą nazwą pakietu, ale z semantyką JUnit.

Zauważ, że te IDE wygenerują testy dla metod w odp.proji utworzą odpowiedni folder dla metod testowych, których nie ma.

peter.murray.rust
źródło
5

Kiedy robię to w IntelliJ, moje drzewo źródłowe wygląda następująco:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here
duffymo
źródło
4

EDYCJA: Jeśli w Javie nie ma koncepcji podpakietu, czy można to obejść? Mam pewne metody, które chcę, aby były dostępne tylko dla testerów i innych członków tego pakietu.

Prawdopodobnie zależy to trochę od twoich motywów ich niewyświetlania, ale jeśli jedynym powodem jest to, że nie chcesz zanieczyszczać publicznego interfejsu rzeczami przeznaczonymi tylko do testowania (lub inną wewnętrzną rzeczą), umieściłbym te metody w oddzielny interfejs publiczny i niech konsumenci metod „ukrytych” używają tego interfejsu. Nie powstrzyma to innych przed korzystaniem z interfejsu, ale nie widzę powodu, dla którego powinieneś.

W przypadku testów jednostkowych i jeśli jest to możliwe bez przepisywania partii, postępuj zgodnie z sugestiami, aby użyć tego samego pakietu.

Fredrik
źródło
3

Jak wyjaśnili inni, w Javie nie ma czegoś takiego jak „podpakiet”: wszystkie pakiety są izolowane i nie dziedziczą niczego po swoich rodzicach.

Łatwym sposobem uzyskania dostępu do chronionych elementów członkowskich z innego pakietu jest rozszerzenie klasy i zastąpienie elementów członkowskich.

Na przykład, aby uzyskać dostęp ClassInAw pakiecie a.b:

package a;

public class ClassInA{
    private final String data;

    public ClassInA(String data){ this.data = data; }

    public String getData(){ return data; }

    protected byte[] getDataAsBytes(){ return data.getBytes(); }

    protected char[] getDataAsChars(){ return data.toCharArray(); }
}

utwórz klasę w tym pakiecie, która przesłania metody, których potrzebujesz w ClassInA:

package a.b;

import a.ClassInA;

public class ClassInAInB extends ClassInA{
    ClassInAInB(String data){ super(data); }

    @Override
    protected byte[] getDataAsBytes(){ return super.getDataAsBytes(); }
}

Dzięki temu możesz użyć klasy nadpisującej zamiast klasy z innego pakietu:

package a.b;

import java.util.Arrays;

import a.ClassInA;

public class Driver{
    public static void main(String[] args){
        ClassInA classInA = new ClassInA("string");
        System.out.println(classInA.getData());
        // Will fail: getDataAsBytes() has protected access in a.ClassInA
        System.out.println(Arrays.toString(classInA.getDataAsBytes()));

        ClassInAInB classInAInB = new ClassInAInB("string");
        System.out.println(classInAInB.getData());
        // Works: getDataAsBytes() is now accessible
        System.out.println(Arrays.toString(classInAInB.getDataAsBytes()));
    }
}

Zauważ, że działa to tylko dla chronionych elementów członkowskich, które są widoczne dla klas rozszerzających (dziedziczenie), a nie dla prywatnych członków pakietu, które są widoczne tylko dla klas podrzędnych / rozszerzających w tym samym pakiecie. Mam nadzieję, że to komuś pomoże!

ndm13
źródło
3

Większość odpowiedzi stwierdzała, że ​​w Javie nie ma czegoś takiego jak podpakiet, ale nie jest to do końca dokładne. Termin ten znajdował się w specyfikacji języka Java już w Javie 6 i prawdopodobnie później (nie wydaje się, aby istniała swobodnie dostępna wersja JLS dla wcześniejszych wersji Java). Język wokół podpakietów nie zmienił się zbytnio w JLS od czasu Java 6.

Java 13 JLS :

Składowymi pakietu są jego podpakiety oraz wszystkie typy klas najwyższego poziomu i typy interfejsów najwyższego poziomu zadeklarowane we wszystkich jednostkach kompilacji pakietu.

Na przykład w Java SE Platform API:

  • Pakiet javama podpakietów awt, applet, io, lang, net, i utiljednostki, ale nie kompilacji.
  • Pakiet java.awtma nazwany podpakiet image, a także kilka jednostek kompilacji zawierających deklaracje typów klas i interfejsów.

Koncepcja podpakietu jest istotna, ponieważ wymusza ograniczenia nazewnictwa między pakietami i klasami / interfejsami:

Pakiet nie może zawierać dwóch elementów członkowskich o tej samej nazwie lub może wystąpić błąd w czasie kompilacji.

Oto kilka przykładów:

  • Ponieważ pakiet java.awtzawiera podpakiet image, nie może (i nie zawiera) deklaracji klasy lub typu interfejsu o nazwie image.
  • Jeśli w pakiecie znajduje się pakiet o nazwie mousei typ elementu członkowskiego Button(który następnie może być określany jako mouse.Button), nie może istnieć żaden pakiet o w pełni kwalifikowanej nazwie mouse.Buttonlub mouse.Button.Click.
  • Jeśli com.nighthacks.java.jagjest w pełni kwalifikowaną nazwą typu, nie może istnieć żaden pakiet, którego w pełni kwalifikowaną nazwą jest albo com.nighthacks.java.jaglub com.nighthacks.java.jag.scrabble.

Jednak to ograniczenie nazewnictwa jest jedynym znaczeniem, jakie język nadaje podpakietom:

Hierarchiczna struktura nazewnictwa paczek ma być wygodna do organizowania powiązanych paczek w konwencjonalny sposób, ale sama w sobie nie ma żadnego znaczenia poza zakazem dotyczącym paczki mającej podpakiet o tej samej nazwie prostej, co typ najwyższego poziomu zadeklarowany w tym pakiecie .

Na przykład nie ma specjalnej relacji dostępu między pakietem o nazwie olivera innym pakietem o nazwie oliver.twistlub między pakietami o nazwie evelyn.woodi evelyn.waugh. Oznacza to, że kod w pakiecie o nazwie oliver.twistnie ma lepszego dostępu do typów zadeklarowanych w pakiecie oliverniż kod w jakimkolwiek innym pakiecie.


W tym kontekście możemy odpowiedzieć na samo pytanie. Ponieważ wyraźnie nie ma specjalnej relacji dostępu między pakietem a jego podpakietem lub między dwoma różnymi podpakietami pakietu nadrzędnego, w języku nie ma możliwości, aby metoda była widoczna dla dwóch różnych pakietów w żądany sposób. Jest to udokumentowana, celowa decyzja projektowa.

Albo metodę można upublicznić, a wszystkie pakiety (w tym odp.proji odp.proj.test) będą mogły uzyskać dostęp do podanych metod, albo metoda może zostać ustawiona jako prywatna (domyślna widoczność), a cały kod, który musi uzyskać bezpośredni dostęp, musi zostać umieszczony ten sam (pod) pakiet co metoda.

To powiedziawszy, bardzo standardową praktyką w Javie jest umieszczanie kodu testowego w tym samym pakiecie co kod źródłowy, ale w innym miejscu w systemie plików. Na przykład w narzędziu do kompilacji Maven konwencja będzie taka, aby umieścić te pliki źródłowe i testowe odpowiednio w src/main/java/odp/proji src/test/java/odp/proj. Kiedy narzędzie kompilacji to kompiluje, oba zestawy plików trafiają do odp.projpakietu, ale tylko te srcpliki są uwzględniane w artefakcie produkcyjnym; pliki testowe są używane tylko w czasie kompilacji do weryfikacji plików produkcyjnych. Dzięki tej konfiguracji kod testowy może swobodnie uzyskiwać dostęp do dowolnego kodu prywatnego lub chronionego kodu testowanego kodu, ponieważ będą one znajdować się w tym samym pakiecie.

W przypadku, gdy chcesz udostępniać kod między podpakietami lub pakietami rodzeńskimi, które nie są przypadkiem testowym / produkcyjnym, jednym z rozwiązań, które widziałem w niektórych bibliotekach, jest umieszczenie tego udostępnionego kodu jako publicznego, ale udokumentuj, że jest on przeznaczony dla biblioteki wewnętrznej tylko do użytku.

M. Justin
źródło
0

Bez umieszczania modyfikatora dostępu przed metodą mówisz, że jest to pakiet prywatny.
Spójrz na poniższy przykład.

package odp.proj;
public class A
{
    void launchA() { }
}

package odp.proj.test;
public class B
{
    void launchB() { }
}

public class Test
{
    public void test()
    {
        A a = new A();
        a.launchA()    // cannot call launchA because it is not visible
    }
}
Alberto Zaccagni
źródło
0

Z klasą PackageVisibleHelper i zachowując ją jako prywatną przed zamrożeniem PackageVisibleHelperFactory, możemy wywołać metodę launchA (przez PackageVisibleHelper) w dowolnym miejscu :)

package odp.proj;
public class A
 {
    void launchA() { }
}

public class PackageVisibleHelper {

    private final PackageVisibleHelperFactory factory;

    public PackageVisibleHelper(PackageVisibleHelperFactory factory) {
        super();
        this.factory = factory;
    }

    public void launchA(A a) {
        if (factory == PackageVisibleHelperFactory.INSTNACNE && !factory.isSampleHelper(this)) {
            throw new IllegalAccessError("wrong PackageVisibleHelper ");
        }
        a.launchA();
    }
}


public class PackageVisibleHelperFactory {

    public static final PackageVisibleHelperFactory INSTNACNE = new PackageVisibleHelperFactory();

    private static final PackageVisibleHelper HELPER = new PackageVisibleHelper(INSTNACNE);

    private PackageVisibleHelperFactory() {
        super();
    }

    private boolean frozened;

    public PackageVisibleHelper getHelperBeforeFrozen() {
        if (frozened) {
            throw new IllegalAccessError("please invoke before frozen!");
        }
        return HELPER;
    }

    public void frozen() {
        frozened = true;
    }

    public boolean isSampleHelper(PackageVisibleHelper helper) {
        return HELPER.equals(helper);
    }
}
package odp.proj.test;

import odp.proj.A;
import odp.proj.PackageVisibleHelper;
import odp.proj.PackageVisibleHelperFactory;

public class Test {

    public static void main(String[] args) {

        final PackageVisibleHelper helper = PackageVisibleHelperFactory.INSTNACNE.getHelperBeforeFrozen();
        PackageVisibleHelperFactory.INSTNACNE.frozen();


        A a = new A();
        helper.launchA(a);

        // illegal access       
        new PackageVisibleHelper(PackageVisibleHelperFactory.INSTNACNE).launchA(a); 
    }
}
qxo
źródło