Czy istnieje sposób na symulację koncepcji „przyjaciela” C ++ w Javie?

196

Chciałbym móc napisać klasę Java w jednym pakiecie, która może uzyskać dostęp do niepublicznych metod klasy w innym pakiecie bez konieczności tworzenia podklasy drugiej klasy. czy to możliwe?

Matthew Murdoch
źródło

Odpowiedzi:

466

Oto mała sztuczka, której używam w JAVA do replikacji mechanizmu przyjaciela C ++.

Powiedzmy, że mam klasę Romeoi inną klasę Juliet. Są w różnych opakowaniach (rodzinnych) z powodów nienawiści.

Romeochce cuddle Julieti Julietchce tylko Romeo cuddlejej pozwolić .

W C ++ Julietzadeklarowałbym Romeojako (kochanek), friendale java nie ma takich rzeczy.

Oto klasy i sztuczka:

Panie po pierwsze:

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }

}

Więc metoda Juliet.cuddlejest taka, publicale trzeba Romeo.Loveją nazwać. Używa tego Romeo.Lovejako „zabezpieczenia podpisu”, aby upewnić się, że Romeomoże wywołać tę metodę i sprawdza, czy miłość jest prawdziwa, aby środowisko wykonawcze wyrzuciło, NullPointerExceptionjeśli tak jest null.

Teraz chłopcy:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();

    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

Klasa Romeo.Lovejest publiczna, ale jej konstruktorem jest private. Dlatego każdy może to zobaczyć, ale tylko Romeogo zbudować. Używam statycznego odniesienia, więc ten, Romeo.Lovektóry nigdy nie jest używany, jest budowany tylko raz i nie wpływa na optymalizację.

Dlatego Romeomoże cuddle Julieti tylko on może, ponieważ tylko on może zbudować Romeo.Loveinstancję i uzyskać do niej dostęp , co jest Julietdla cuddleniej wymagane (w przeciwnym razie da ci klapsa NullPointerException).

Salomon BRYS
źródło
107
+1 za „policzek z wyjątkiem NullPointerException”. Bardzo imponujące.
Nickolas
2
@Steazy Istnieją: poszukaj adnotacji NotNull, NonNull i CheckForNull. Sprawdź w dokumentacji IDE, aby dowiedzieć się, jak używać i wymuszać te adnotacje. Wiem, że IntelliJ domyślnie to osadza i że zaćmienie wymaga wtyczki (takiej jak FindBugs).
Salomon BRYS,
27
Można zrobić Romeo„s Lovedo Juliawiecznego poprzez zmianę lovepola będzie final;-).
Matthias,
5
@Matthias Pole miłości jest statyczne ... Przeredaguję odpowiedź, aby była ostateczna;)
Salomon BRYS
12
Wszystkie odpowiedzi powinny wyglądać tak (Y) +1 dla humoru i świetnego przykładu.
Zia Ul Rehman Mughal
54

Projektanci Java wyraźnie odrzucili pomysł przyjaciela, ponieważ działa on w C ++. Umieszczasz swoich „przyjaciół” w tym samym pakiecie. Prywatne, chronione i pakowane zabezpieczenia są wymuszane jako część projektu językowego.

James Gosling chciał, aby Java była C ++ bez błędów. Wierzę, że czuł, że przyjaciel był błędem, ponieważ narusza zasady OOP. Pakiety zapewniają rozsądny sposób organizowania komponentów bez zbytniego purystycznego podejścia do OOP.

NR wskazał, że można oszukiwać za pomocą odbicia, ale nawet to działa tylko wtedy, gdy nie używasz SecurityManager. Jeśli włączysz standardowe zabezpieczenia Java, nie będziesz mógł oszukiwać za pomocą refleksji, chyba że napiszesz zasady bezpieczeństwa, które specjalnie na to pozwalają.

David G.
źródło
11
Nie chcę być pedantem, ale modyfikatory dostępu nie są mechanizmem bezpieczeństwa.
Greg D
6
Modyfikatory dostępu są częścią modelu bezpieczeństwa Java. W szczególności miałem na myśli java.lang.RuntimePermission do refleksji: accessDeclaredMembers i accessClassInPackage.
David G,
54
Jeśli Gosling naprawdę myślał, że friendnaruszył OOP (w szczególności więcej niż dostęp do pakietu), to tak naprawdę go nie zrozumiał (całkowicie możliwe, wiele osób źle to rozumie).
Konrad Rudolph
8
Komponenty klasy czasami muszą być oddzielone (np. Implementacja i API, główny obiekt i adapter). Ochrona na poziomie pakietu jest jednocześnie zbyt liberalna i zbyt restrykcyjna, aby zrobić to poprawnie.
opóźniony
2
@GregD Można je uznać za mechanizm bezpieczeństwa w tym sensie, że pomagają zapobiegać nieprawidłowemu użyciu członka klasy przez programistów. Myślę, że prawdopodobnie lepiej byłoby je nazwać mechanizmem bezpieczeństwa .
zmiażdżyć
45

Koncepcja „przyjaciela” jest przydatna na przykład w Javie do oddzielenia API od jego implementacji. Klasy implementacji często potrzebują dostępu do elementów wewnętrznych klasy API, ale nie powinny one być udostępniane klientom API. Można to osiągnąć za pomocą wzoru „Friend Accessor”, jak opisano poniżej:

Klasa wystawiona przez API:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

Klasa zapewniająca funkcjonalność „przyjaciela”:

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

Przykładowy dostęp z klasy w pakiecie implementacyjnym „przyjaciel”:

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
Matthew Murdoch
źródło
1
Ponieważ nie wiedziałem, co oznacza „statyczny” w klasie „Exposed”: Blok statyczny to blok instrukcji wewnątrz klasy Java, który zostanie wykonany, gdy klasa zostanie po raz pierwszy załadowana do JVM. Czytaj więcej na javatutorialhub. com /…
Guy L
Ciekawy wzorzec, ale wymaga, aby klasy Exposed i Accessor były publiczne, podczas gdy klasy implementujące API (tj. Zestaw klas Java implementujących zestaw publicznych interfejsów Java) lepiej byłyby „domyślnie chronione”, a zatem niedostępne dla klienta oddzielić typy od ich implementacji.
Yann-Gaël Guéhéneuc
8
Jestem dość zardzewiały na mojej Javie, więc wybacz moją ignorancję. Jakie są zalety tego rozwiązania w porównaniu z rozwiązaniem „Romeo i Julia” opublikowanym przez Salomona BRYS? Ta implementacja odstraszyłaby ode mnie spodnie, gdybym natknęła się na nią w bazie kodu (bez twojego wyjaśnienia, tj. Ciężkiego komentowania). Podejście Romeo i Julii jest bardzo łatwe do zrozumienia.
Steazy
1
Takie podejście sprawi, że problemy będą widoczne tylko w czasie wykonywania, podczas gdy niewłaściwe użycie Romeo i Julii sprawi, że będą widoczne w czasie kompilacji, podczas rozwoju.
ymajoros
1
@ymajoros Przykład Romeo i Julii nie sprawia, że ​​nadużycie jest widoczne w czasie kompilacji. Opiera się na poprawnym przekazaniu argumentu i zgłoszeniu wyjątku. Oba są działaniami w czasie wykonywania.
Radiodef
10

Istnieją dwa rozwiązania twojego pytania, które nie wymagają trzymania wszystkich klas w tym samym pakiecie.

Pierwszym z nich jest użycie wzorca Friend Accessor / Friend Package opisanego w (Practical API Design, Tulach 2008).

Drugim jest użycie OSGi. Jest tu artykuł wyjaśniający, jak OSGi to osiąga.

Powiązane pytania: 1 , 2 i 3 .

Jeff Axelrod
źródło
7

O ile mi wiadomo, nie jest to możliwe.

Może mógłbyś podać nam więcej szczegółów na temat swojego projektu. Takie pytania są prawdopodobnie wynikiem wad projektowych.

Zastanów się

  • Dlaczego te klasy są w różnych pakietach, jeśli są tak blisko powiązane?
  • Czy A ma dostęp do prywatnych członków B, czy też operacja powinna zostać przeniesiona do klasy B i uruchomiona przez A?
  • Czy to naprawdę dzwoni, czy też obsługa zdarzeń jest lepsza?
czarny
źródło
3

Odpowiedź eirikma jest łatwa i doskonała. Mógłbym dodać jeszcze jedną rzecz: zamiast mieć publicznie dostępną metodę getFriend (), aby uzyskać przyjaciela, którego nie można użyć, możesz pójść o krok dalej i zabronić zdobywania przyjaciela bez tokena: getFriend (Service.FriendToken). Ten FriendToken byłby wewnętrzną klasą publiczną z prywatnym konstruktorem, tak aby tylko usługa mogła go utworzyć.

AriG
źródło
3

Oto wyraźny przykład użycia z Friendklasą wielokrotnego użytku . Zaletą tego mechanizmu jest prostota użytkowania. Może to dobre dla zapewnienia klasom testów jednostkowych większego dostępu niż reszta aplikacji.

Na początek poniżej znajduje się przykład korzystania z Friendklasy.

public class Owner {
    private final String member = "value";

    public String getMember(final Friend friend) {
        // Make sure only a friend is accepted.
        friend.is(Other.class);
        return member;
    }
}

Następnie w innym pakiecie możesz to zrobić:

public class Other {
    private final Friend friend = new Friend(this);

    public void test() {
        String s = new Owner().getMember(friend);
        System.out.println(s);
    }
}

FriendKlasa jest następujący.

public final class Friend {
    private final Class as;

    public Friend(final Object is) {
        as = is.getClass();
    }

    public void is(final Class c) {
        if (c == as)
            return;
        throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
    }

    public void is(final Class... classes) {
        for (final Class c : classes)
            if (c == as)
                return;
        is((Class)null);
    }
}

Problem polega jednak na tym, że można go nadużywać w następujący sposób:

public class Abuser {
    public void doBadThings() {
        Friend badFriend = new Friend(new Other());
        String s = new Owner().getMember(badFriend);
        System.out.println(s);
    }
}

Teraz może być prawdą, że Otherklasa nie ma żadnych publicznych konstruktorów, przez co powyższy Abuserkod jest niemożliwy. Jednakże, jeśli klasa nie ma konstruktora publicznego to chyba wskazane, aby powielić klasę Friend jako wewnętrzna klasy. Weź tę Other2klasę jako przykład:

public class Other2 {
    private final Friend friend = new Friend();

    public final class Friend {
        private Friend() {}
        public void check() {}
    }

    public void test() {
        String s = new Owner2().getMember(friend);
        System.out.println(s);
    }
}

A potem Owner2klasa wyglądałaby tak:

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Zauważ, że Other2.Friendklasa ma prywatnego konstruktora, dzięki czemu jest to o wiele bezpieczniejszy sposób.

intrepidis
źródło
2

Podane rozwiązanie może nie było najprostsze. Inne podejście opiera się na tym samym pomyśle, co w C ++: członkowie prywatni nie są dostępni poza pakietem / zakresem prywatnym, z wyjątkiem określonej klasy, którą właściciel sam zaprzyjaźnia.

Klasa, która potrzebuje dostępu znajomego do członka, powinna utworzyć wewnętrzną abstrakcyjną „klasę znajomego”, do której klasa będąca właścicielem ukrytych właściwości może eksportować dostęp, zwracając podklasę, która implementuje metody implementujące dostęp. Metoda „API” klasy znajomego może być prywatna, więc nie jest dostępna poza klasą, która wymaga dostępu znajomego. Jego jedynym stwierdzeniem jest wywołanie abstrakcyjnego elementu chronionego, który implementuje klasa eksportująca.

Oto kod:

Najpierw test sprawdzający, czy to faktycznie działa:

package application;

import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;

public class EntityFriendTest extends TestCase {
    public void testFriendsAreOkay() {
        Entity entity = new Entity();
        Service service = new Service();
        assertNull("entity should not be processed yet", entity.getPublicData());
        service.processEntity(entity);
        assertNotNull("entity should be processed now", entity.getPublicData());
    }
}

Następnie Usługa, która potrzebuje dostępu znajomego do pakietu prywatnego członka Entity:

package application.service;

import application.entity.Entity;

public class Service {

    public void processEntity(Entity entity) {
        String value = entity.getFriend().getEntityPackagePrivateData();
        entity.setPublicData(value);
    }

    /**
     * Class that Entity explicitly can expose private aspects to subclasses of.
     * Public, so the class itself is visible in Entity's package.
     */
    public static abstract class EntityFriend {
        /**
         * Access method: private not visible (a.k.a 'friendly') outside enclosing class.
         */
        private String getEntityPackagePrivateData() {
            return getEntityPackagePrivateDataImpl();
        }

        /** contribute access to private member by implementing this */
        protected abstract String getEntityPackagePrivateDataImpl();
    }
}

Wreszcie: klasa Entity, która zapewnia przyjazny dostęp do prywatnego członka pakietu tylko do klasy application.service.Service.

package application.entity;

import application.service.Service;

public class Entity {

    private String publicData;
    private String packagePrivateData = "secret";   

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    String getPackagePrivateData() {
        return packagePrivateData;
    }

    /** provide access to proteced method for Service'e helper class */
    public Service.EntityFriend getFriend() {
        return new Service.EntityFriend() {
            protected String getEntityPackagePrivateDataImpl() {
                return getPackagePrivateData();
            }
        };
    }
}

Okej, muszę przyznać, że jest to trochę dłużej niż „obsługa znajomych :: usługa;” ale możliwe jest skrócenie go przy jednoczesnym zachowaniu sprawdzania czasu kompilacji za pomocą adnotacji.

eirikma
źródło
To nie do końca działa, ponieważ normalna klasa w tym samym pakiecie mogłaby po prostu uzyskać funkcję GetFriend (), a następnie wywołać metodę chronioną, omijając prywatną.
user2219808
1

W Javie można mieć „przyjazność związaną z pakietami”. Może to być przydatne do testowania jednostkowego. Jeśli nie określisz metody private / public / protected przed metodą, będzie to „przyjaciel w pakiecie”. Klasa w tym samym pakiecie będzie mogła uzyskać do niej dostęp, ale będzie prywatna poza klasą.

Ta reguła nie zawsze jest znana i jest dobrym przybliżeniem słowa kluczowego „przyjaciel” w języku C ++. Uważam, że to dobry zamiennik.

daitangio
źródło
1
To prawda, ale naprawdę pytałem o kod znajdujący się w różnych pakietach ...
Matthew Murdoch,
1

Myślę, że klasy zaprzyjaźnione w C ++ są jak koncepcja klasy wewnętrznej w Javie. Za pomocą klas wewnętrznych możesz zdefiniować klasę zamykającą i klasę zamkniętą. Klasa zamknięta ma pełny dostęp do publicznych i prywatnych członków jej klasy zamkniętej. zobacz następujący link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Sefirot
źródło
Nie, nie są. To bardziej przypomina przyjaźń w prawdziwym życiu: może, ale nie musi być, wzajemne (bycie przyjacielem B nie oznacza, że ​​B jest uważany za przyjaciela A), a ty i twoi przyjaciele możecie być z zupełnie innych powodów rodziny i mieć własne, być może (ale niekoniecznie) pokrywające się kręgi przyjaciół. (Nie to, że chciałbym zobaczyć zajęcia z wieloma przyjaciółmi. Może to być przydatna funkcja, ale należy z niej korzystać ostrożnie.)
Christopher Creutzig
1

Myślę, że podejście polegające na użyciu wzorca dostępu do przyjaciela jest zbyt skomplikowane. Musiałem zmierzyć się z tym samym problemem i rozwiązałem go za pomocą dobrego, starego konstruktora kopii znanego z C ++ w Javie:

public class ProtectedContainer {
    protected String iwantAccess;

    protected ProtectedContainer() {
        super();
        iwantAccess = "Default string";
    }

    protected ProtectedContainer(ProtectedContainer other) {
        super();
        this.iwantAccess = other.iwantAccess;
    }

    public int calcSquare(int x) {
        iwantAccess = "calculated square";
        return x * x;
    }
}

W swojej aplikacji możesz napisać następujący kod:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

        protected PrivateAccessor(ProtectedContainer prot) {
            super(prot);
        }

        public String exposeProtected() {
            return iwantAccess;
        }
    }
}

Zaletą tej metody jest to, że tylko twoja aplikacja ma dostęp do chronionych danych. Nie jest to dokładnie podstawienie słowa kluczowego znajomego. Ale myślę, że jest to całkiem odpowiednie, gdy piszesz niestandardowe biblioteki i potrzebujesz dostępu do chronionych danych.

Ilekroć masz do czynienia z wystąpieniami ProtectedContainer, możesz owinąć wokół niego ProtectedAccessor i uzyskać dostęp.

Działa również z metodami chronionymi. Zdefiniujesz je chronione w interfejsie API. Później w aplikacji piszesz prywatną klasę opakowania i udostępniasz chronioną metodę jako publiczną. Otóż ​​to.

Chris
źródło
1
Ale ProtectedContainermożna go sklasyfikować poza pakietem!
Raphael,
0

Jeśli chcesz uzyskać dostęp do metod chronionych, możesz utworzyć podklasę klasy, której chcesz użyć, która ujawnia metody, których chcesz używać jako publicznego (lub wewnętrznego w przestrzeni nazw, aby być bezpieczniejszym) i mieć instancję tej klasy w swojej klasie (użyj go jako proxy).

Jeśli chodzi o metody prywatne (myślę) nie masz szczęścia.

Omar Kooheji
źródło
0

Zgadzam się, że w większości przypadków słowo kluczowe znajomego jest niepotrzebne.

  • Package-private (alias. Default) jest wystarczający w większości przypadków, gdy masz grupę silnie powiązanych ze sobą klas
  • W przypadku klas debugowania, które chcą uzyskać dostęp do elementów wewnętrznych, zazwyczaj ustawiam metodę jako prywatną i uzyskuję do niej dostęp poprzez refleksję. Szybkość zwykle nie jest tu ważna
  • Czasami implementujesz metodę, która jest „włamaniem” lub w inny sposób, który może ulec zmianie. Podaję to do wiadomości publicznej, ale używam @Deprecated, aby wskazać, że nie powinieneś polegać na tej metodzie.

I wreszcie, jeśli to naprawdę konieczne, istnieje wzorzec akcesorium przyjaciela wymieniony w innych odpowiedziach.

Casebash
źródło
0

Nie używasz słowa kluczowego lub czegoś podobnego.

Możesz „oszukiwać” za pomocą refleksji itp., Ale nie polecam „oszukiwania”.

NR
źródło
3
uznałbym to za tak zły pomysł, że nawet sugerowanie, że jest dla mnie odrażające. Oczywiście jest to w najlepszym wypadku grudka i nie powinna być częścią żadnego projektu.
shsteimer
0

Znalezioną przeze mnie metodą rozwiązania tego problemu jest utworzenie obiektu akcesora, na przykład:

class Foo {
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* This is the accessor. Anyone with a reference to this has special access. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    /** You get an accessor by calling this method. This method can only
     * be called once, so calling is like claiming ownership of the accessor. */
    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }
}

Pierwszy kod wywołujący getAccessor()„roszczenia własności” akcesora. Zwykle jest to kod, który tworzy obiekt.

Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.

Ma to również przewagę nad mechanizmem przyjaciela C ++, ponieważ pozwala ograniczyć dostęp na poziomie instancji , a nie na poziomie klasy . Kontrolując odwołanie do akcesorium, kontrolujesz dostęp do obiektu. Możesz także utworzyć wiele akcesorów i dać inny dostęp do każdego z nich, co pozwala na szczegółową kontrolę nad tym, jaki kod może uzyskać dostęp do:

class Foo {
    private String secret;
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* Normal accessor. Can write to locked, but not read secret. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }

    /* Super accessor. Allows access to secret. */
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    private FooSuperAccessor superAccessor;

    public FooSuperAccessor getAccessor() {
        if (superAccessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return superAccessor = new FooSuperAccessor();
    }
}

Wreszcie, jeśli chcesz, aby rzeczy były nieco lepiej zorganizowane, możesz utworzyć obiekt referencyjny, który trzyma wszystko razem. Dzięki temu możesz przejąć wszystkie akcesory za pomocą jednego wywołania metody, a także zachować je razem z ich połączoną instancją. Po uzyskaniu referencji możesz przekazać akcesoriom kod, który jej potrzebuje:

class Foo {
    private String secret;
    private String locked;

    public String getLocked() { return locked; }

    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    public class FooReference {
        public final Foo foo;
        public final FooAccessor accessor;
        public final FooSuperAccessor superAccessor;

        private FooReference() {
            this.foo = Foo.this;
            this.accessor = new FooAccessor();
            this.superAccessor = new FooSuperAccessor();
        }
    }

    private FooReference reference;

    /* Beware, anyone with this object has *all* the accessors! */
    public FooReference getReference() {
        if (reference != null)
            throw new IllegalStateException("Cannot return reference more than once!");
        return reference = new FooReference();
    }
}

Po wielu uderzeniach w głowę (nie w dobrym tego rodzaju) było to moje ostateczne rozwiązanie i bardzo mi się podoba. Jest elastyczny, prosty w użyciu i umożliwia bardzo dobrą kontrolę dostępu do klasy. ( Dostęp tylko z referencją jest bardzo przydatny.) Jeśli używasz chronionego zamiast prywatnego dla akcesorów / referencji, podklasy Foo mogą nawet zwracać rozszerzone referencje z getReference. Nie wymaga również refleksji, więc można go używać w dowolnym środowisku.

jpfx1342
źródło
0

Począwszy od wersji Java 9, w wielu przypadkach można używać modułów, aby nie stanowiły problemu.

Raphael
źródło
0

Wolę delegację, skład lub klasę fabryczną (w zależności od problemu, który powoduje ten problem), aby uniknąć uczynienia go klasą publiczną.

Jeśli jest to problem z „klasami interfejsów / implementacji w różnych pakietach”, wówczas użyłbym publicznej klasy fabrycznej, która byłaby w tym samym pakiecie co pakiet impl i zapobiegałaby ujawnieniu klasy impl.

Jeśli jest to problem „Nienawidzę upubliczniać tej klasy / metody tylko po to, aby zapewnić tę funkcjonalność dla innej klasy w innym pakiecie”, wówczas użyłbym publicznej klasy delegowanej w tym samym pakiecie i ujawniłbym tylko tę część funkcjonalności potrzebne klasie „outsider”.

Niektóre z tych decyzji wynikają z architektury ładowania klas serwera docelowego (pakiet OSGi, WAR / EAR itp.), Konwencji wdrażania i nazewnictwa pakietów. Na przykład wyżej zaproponowane rozwiązanie, wzorzec „Friend Accessor” jest sprytny w przypadku normalnych aplikacji Java. Zastanawiam się, czy trudno jest zaimplementować go w OSGi ze względu na różnicę w stylu ładowania klas.

Suraj Rao
źródło
0

Nie wiem, czy jest to przydatne dla kogokolwiek, ale poradziłem sobie z tym w następujący sposób:

Stworzyłem interfejs (AdminRights).

Każda klasa, która powinna mieć możliwość wywoływania wspomnianych funkcji, powinna implementować prawa administratora.

Następnie utworzyłem funkcję HasAdminRights w następujący sposób:

private static final boolean HasAdminRights()
{
    // Gets the current hierarchy of callers
    StackTraceElement[] Callers = new Throwable().getStackTrace();

    // Should never occur with me but if there are less then three StackTraceElements we can't check
    if (Callers.length < 3)
    {
        EE.InvalidCode("Couldn't check for administrator rights");
        return false;

    } else try
    {

        // Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
        Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);

        // If everything worked up to now, it has admin rights!
        return true;

    } catch (java.lang.ClassCastException | ClassNotFoundException e)
    {
        // In the catch, something went wrong and we can deduce that the caller has no admin rights

        EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
        return false;
    }
}
mdre
źródło
-1

Kiedyś widziałem rozwiązanie oparte na odbiciu, które „sprawdzało przyjaciela” w czasie wykonywania przy użyciu odbicia i sprawdzania stosu wywołań, aby sprawdzić, czy klasa wywołująca metodę może to zrobić. Jako kontrola czasu wykonywania ma oczywistą wadę.

Ran Biron
źródło