Gibt es eine Möglichkeit, das C ++ 'Freund'-Konzept in Java zu simulieren?


196

Ich möchte in der Lage sein, eine Java-Klasse in ein Paket zu schreiben, die auf nicht öffentliche Methoden einer Klasse in einem anderen Paket zugreifen kann, ohne sie zu einer Unterklasse der anderen Klasse machen zu müssen. Ist das möglich?

Antworten:


466

Hier ist ein kleiner Trick, den ich in JAVA verwende, um den C ++ - Freundmechanismus zu replizieren.

Nehmen wir an, ich habe eine Klasse Romeound eine andere Klasse Juliet. Sie sind aus Hassgründen in verschiedenen Paketen (Familien).

Romeowill cuddle Julietund Julietwill sie nur lassen Romeo cuddle.

In C ++ Julietwürde Romeoals (Liebhaber) deklarieren, friendaber es gibt keine solchen Dinge in Java.

Hier sind die Klassen und der Trick:

Frauen zuerst :

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?");
    }

}

Die Methode Juliet.cuddleist also, publicaber Sie brauchen eine Romeo.Love, um sie aufzurufen. Es verwendet dies Romeo.Loveals "Signatursicherheit", um sicherzustellen, dass nur Romeodiese Methode aufgerufen werden kann, und überprüft, ob die Liebe echt ist, so dass die Laufzeit eine auslöst, NullPointerExceptionwenn dies der Fall ist null.

Jetzt Jungs:

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

Die Klasse Romeo.Loveist öffentlich, ihr Konstruktor jedoch private. Daher kann es jeder sehen, aber nur Romeokonstruieren. Ich verwende eine statische Referenz, sodass die Romeo.Lovenie verwendete nur einmal erstellt wird und keine Auswirkungen auf die Optimierung hat.

Daher Romeokann cuddle Julietund nur er kann , denn nur so kann er bauen und Zugang eine Romeo.LoveInstanz, die durch erforderlich ist , Julietum cuddlesie (sonst werden sie dich mit einem Schlag NullPointerException).


107
+1 für "Schlag dich mit einer NullPointerException". Sehr beeindruckend.
Nickolas

2
@Steazy Es gibt: Suchen Sie nach den Annotationen NotNull, NonNull und CheckForNull. Informationen zur Verwendung und Durchsetzung dieser Anmerkungen finden Sie in der Dokumentation Ihrer IDE. Ich weiß, dass IntelliJ dies standardmäßig einbettet und dass Eclipse ein Plugin benötigt (wie z. B. FindBugs).
Salomon BRYS

27
Sie können machen Romeos‘ Lovefür Juliaewige durch die sich verändernde loveFeld sein final;-).
Matthias

5
@ Matthias Das Liebesfeld ist statisch ... Ich werde die Antwort bearbeiten, um sie endgültig zu machen;)
Salomon BRYS

12
Alle Antworten sollten für den Humor und das gute Beispiel so sein (Y) +1.
Zia Ul Rehman Mughal

54

Die Designer von Java lehnten die Idee eines Freundes ausdrücklich ab, da es in C ++ funktioniert. Sie legen Ihre "Freunde" in das gleiche Paket. Private, geschützte und verpackte Sicherheit wird als Teil des Sprachdesigns erzwungen.

James Gosling wollte, dass Java ohne Fehler C ++ ist. Ich glaube, er hatte das Gefühl, dass ein Freund ein Fehler war, weil er gegen die OOP-Prinzipien verstößt. Pakete bieten eine vernünftige Möglichkeit, Komponenten zu organisieren, ohne OOP zu puristisch zu sehen.

NR wies darauf hin, dass Sie mit Reflection schummeln könnten, aber selbst das funktioniert nur, wenn Sie den SecurityManager nicht verwenden. Wenn Sie die Java-Standardsicherheit aktivieren, können Sie nur dann mit Reflexion betrügen, wenn Sie eine Sicherheitsrichtlinie schreiben, die dies ausdrücklich zulässt.


11
Ich möchte kein Pedant sein, aber Zugriffsmodifikatoren sind kein Sicherheitsmechanismus.
Greg D

6
Zugriffsmodifikatoren sind Teil des Java-Sicherheitsmodells. Ich habe mich speziell auf java.lang.RuntimePermission bezogen, um darüber nachzudenken: accessDeclaredMembers und accessClassInPackage.
David G

54
Wenn Gosling wirklich dachte, dass friendOOP verletzt wurde (insbesondere mehr als der Paketzugriff), dann verstand er es wirklich nicht (durchaus möglich, viele Leute verstehen es falsch).
Konrad Rudolph

8
Klassenkomponenten müssen manchmal getrennt werden (z. B. Implementierung und API, Kernobjekt und Adapter). Der Schutz auf Paketebene ist gleichzeitig zu freizügig und zu restriktiv, um dies ordnungsgemäß zu tun.
Dhardy

2
@GregD Sie könnten als Sicherheitsmechanismus in dem Sinne angesehen werden, dass sie verhindern, dass Entwickler ein Klassenmitglied falsch verwenden. Ich denke, sie sollten wahrscheinlich besser als Sicherheitsmechanismus bezeichnet werden .
Crush

45

Das "Freund" -Konzept ist in Java beispielsweise nützlich, um eine API von ihrer Implementierung zu trennen. Es ist üblich, dass Implementierungsklassen Zugriff auf API-Klasseninternale benötigen, diese sollten jedoch nicht für API-Clients verfügbar gemacht werden. Dies kann mithilfe des unten beschriebenen Musters "Friend Accessor" erreicht werden:

Die Klasse, die über die API verfügbar gemacht wird:

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

Die Klasse, die die 'Freund'-Funktionalität bereitstellt:

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

Beispielzugriff von einer Klasse im Implementierungspaket 'friend':

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}

1
Weil ich nicht wusste, was "statisch" in der Klasse "Exposed" bedeutet: Der statische Block ist ein Anweisungsblock innerhalb einer Java-Klasse, der ausgeführt wird, wenn eine Klasse zum ersten Mal in die JVM geladen wird.
Guy L

Interessantes Muster, aber es erfordert, dass die Klassen Exposed und Accessor öffentlich sind, während die Klassen, die eine API implementieren (dh eine Reihe von Java-Klassen, die eine Reihe von öffentlichen Java-Schnittstellen implementieren), besser "standardmäßig geschützt" sind und daher für den Client nicht zugänglich sind Typen von ihren Implementierungen zu trennen.
Yann-Gaël Guéhéneuc

8
Ich bin ziemlich verrostet auf meinem Java, also vergib mir meine Unwissenheit. Was ist der Vorteil gegenüber der "Romeo and Juliet" -Lösung, die Salomon BRYS veröffentlicht hat? Diese Implementierung würde mir die Hosen abschrecken, wenn ich in einer Codebasis darauf stoßen würde (ohne Ihre Erklärung im Anhang, dh starke Kommentare). Der Ansatz von Romeo und Julia ist sehr einfach zu verstehen.
Steazy

1
Dieser Ansatz macht Probleme nur zur Laufzeit sichtbar, während der Missbrauch durch Romeo und Julia sie zur Kompilierungszeit während der Entwicklung sichtbar macht.
Ymajoros

1
@ymajoros Das Beispiel von Romeo und Julia macht Missbrauch beim Kompilieren nicht sichtbar. Es beruht darauf, dass ein Argument korrekt übergeben und eine Ausnahme ausgelöst wird. Dies sind beide Laufzeitaktionen.
Radiodef

10

Es gibt zwei Lösungen für Ihre Frage, bei denen nicht alle Klassen im selben Paket bleiben.

Das erste ist die Verwendung des in (Practical API Design, Tulach 2008) beschriebenen Friend Accessor / Friend Package- Musters.

Die zweite ist die Verwendung von OSGi. Es gibt einen Artikel hier erklärt , wie OSGi dies leistet.

Verwandte Fragen: 1 , 2 und 3 .


7

Soweit ich weiß, ist das nicht möglich.

Vielleicht könnten Sie uns weitere Details zu Ihrem Design geben. Fragen wie diese sind wahrscheinlich das Ergebnis von Designfehlern.

Überlegen Sie einfach

  • Warum sind diese Klassen in verschiedenen Paketen, wenn sie so eng miteinander verbunden sind?
  • Hat A Zugriff auf private Mitglieder von B oder sollte die Operation in Klasse B verschoben und von A ausgelöst werden?
  • Ist das wirklich ein Aufruf oder ist die Ereignisbehandlung besser?

3

Die Antwort von eirikma ist einfach und ausgezeichnet. Ich könnte noch etwas hinzufügen: Anstatt eine öffentlich zugängliche Methode zu haben, getFriend (), um einen Freund zu erhalten, der nicht verwendet werden kann, könnten Sie einen Schritt weiter gehen und es nicht zulassen, den Freund ohne Token zu erhalten: getFriend (Service.FriendToken). Dieses FriendToken wäre eine innere öffentliche Klasse mit einem privaten Konstruktor, sodass nur der Dienst eine instanziieren könnte.


3

Hier ist ein klares Anwendungsbeispiel mit einer wiederverwendbaren FriendKlasse. Der Vorteil dieses Mechanismus ist die einfache Verwendung. Vielleicht gut, um Unit-Test-Klassen mehr Zugriff zu gewähren als dem Rest der Anwendung.

Hier ist zunächst ein Beispiel für die Verwendung der FriendKlasse.

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

Dann können Sie dies in einem anderen Paket tun:

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

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

Die FriendKlasse ist wie folgt.

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

Das Problem ist jedoch, dass es wie folgt missbraucht werden kann:

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

Nun kann es wahr sein, dass die OtherKlasse keine öffentlichen Konstruktoren hat, was den obigen AbuserCode unmöglich macht. Wenn Ihre Klasse jedoch über einen öffentlichen Konstruktor verfügt, ist es wahrscheinlich ratsam, die Friend-Klasse als innere Klasse zu duplizieren. Nehmen Sie diese Other2Klasse als Beispiel:

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

Und dann wäre die Owner2Klasse so:

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

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

Beachten Sie, dass die Other2.FriendKlasse einen privaten Konstruktor hat, was dies zu einer viel sichereren Methode macht.


2

Die bereitgestellte Lösung war vielleicht nicht die einfachste. Ein anderer Ansatz basiert auf der gleichen Idee wie in C ++: Auf private Mitglieder kann außerhalb des Pakets / privaten Bereichs nicht zugegriffen werden, mit Ausnahme einer bestimmten Klasse, die der Eigentümer zu einem Freund von sich selbst macht.

Die Klasse, die einen Freundzugriff auf ein Mitglied benötigt, sollte eine innere öffentliche abstrakte "Freundklasse" erstellen, auf die die Klasse, die die verborgenen Eigenschaften besitzt, den Zugriff exportieren kann, indem sie eine Unterklasse zurückgibt, die die Methoden zur Implementierung des Zugriffs implementiert. Die "API" -Methode der Friend-Klasse kann privat sein, sodass außerhalb der Klasse, die einen Friend-Zugriff benötigt, nicht auf sie zugegriffen werden kann. Die einzige Anweisung ist ein Aufruf eines abstrakten geschützten Elements, das die exportierende Klasse implementiert.

Hier ist der Code:

Zuerst der Test, der bestätigt, dass dies tatsächlich funktioniert:

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

Dann der Dienst, der Freund Zugriff auf ein privates Paketmitglied von Entity benötigt:

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

Schließlich: die Entity-Klasse, die einen benutzerfreundlichen Zugriff auf ein privates Paketmitglied nur für die Klasse application.service.Service bietet.

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

Okay, ich muss zugeben, dass es etwas länger ist als "friend service :: Service;" Möglicherweise kann es jedoch verkürzt werden, während die Überprüfung der Kompilierungszeit mithilfe von Anmerkungen beibehalten wird.


Dies funktioniert nicht ganz, da eine normale Klasse im selben Paket einfach getFriend () erhalten und dann die geschützte Methode unter Umgehung der privaten Methode aufrufen könnte.
user2219808

1

In Java ist es möglich, eine "paketbezogene Freundlichkeit" zu haben. Dies kann für Unit-Tests nützlich sein. Wenn Sie nicht privat / öffentlich / geschützt vor einer Methode angeben, ist dies "Freund im Paket". Eine Klasse im selben Paket kann darauf zugreifen, ist jedoch außerhalb der Klasse privat.

Diese Regel ist nicht immer bekannt und eine gute Annäherung an ein C ++ - Schlüsselwort "friend". Ich finde es ein guter Ersatz.


1
Dies ist wahr, aber ich habe wirklich nach Code gefragt, der sich in verschiedenen Paketen befindet ...
Matthew Murdoch

1

Ich denke, dass Freundklassen in C ++ wie ein Konzept der inneren Klasse in Java sind. Mit inneren Klassen können Sie tatsächlich eine einschließende und eine eingeschlossene Klasse definieren. Die geschlossene Klasse hat vollen Zugriff auf die öffentlichen und privaten Mitglieder der einschließenden Klasse. Siehe den folgenden Link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html


Äh, nein, das sind sie nicht. Es ist eher wie eine Freundschaft im wirklichen Leben: Sie kann, muss aber nicht gegenseitig sein (A ein Freund von B zu sein bedeutet nicht, dass B ein Freund von A ist), und Sie und Ihre Freunde können völlig anders sein Familien und haben Ihre eigenen, möglicherweise (aber nicht unbedingt) überlappenden Freundeskreise. (Nicht, dass ich Klassen mit vielen Freunden sehen möchte. Es kann eine nützliche Funktion sein, sollte aber mit Vorsicht verwendet werden.)
Christopher Creutzig

1

Ich denke, der Ansatz, das Friend-Accessor-Muster zu verwenden, ist viel zu kompliziert. Ich musste mich dem gleichen Problem stellen und löste es mit dem guten, alten Kopierkonstruktor, der aus C ++ bekannt ist, in Java:

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

In Ihrer Anwendung können Sie den folgenden Code schreiben:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

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

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

Der Vorteil dieser Methode ist, dass nur Ihre Anwendung Zugriff auf die geschützten Daten hat. Es ist nicht gerade eine Ersetzung des Schlüsselworts friend. Aber ich denke, es ist ziemlich gut geeignet, wenn Sie benutzerdefinierte Bibliotheken schreiben und auf geschützte Daten zugreifen müssen.

Wann immer Sie sich mit Instanzen von ProtectedContainer befassen müssen, können Sie Ihren ProtectedAccessor darum wickeln und erhalten Zugriff.

Es funktioniert auch mit geschützten Methoden. Sie definieren sie geschützt in Ihrer API. Später in Ihrer Anwendung schreiben Sie eine private Wrapper-Klasse und machen die geschützte Methode als öffentlich verfügbar. Das ist es.


1
Aber ProtectedContainerkönnen Unterklasse außerhalb des Pakets sein!
Raphael

0

Wenn Sie auf geschützte Methoden zugreifen möchten, können Sie eine Unterklasse der Klasse erstellen, die Sie verwenden möchten, die die Methoden verfügbar macht, die Sie als öffentlich (oder aus Sicherheitsgründen intern im Namespace) verwenden möchten, und eine Instanz dieser Klasse in Ihrer Klasse haben (Verwenden Sie es als Proxy).

Was private Methoden betrifft (glaube ich), haben Sie kein Glück.


0

Ich bin damit einverstanden, dass das Schlüsselwort friend in den meisten Fällen nicht erforderlich ist.

  • Paket-privat (auch bekannt als Standard) ist in den meisten Fällen ausreichend, wenn Sie eine Gruppe stark miteinander verflochtener Klassen haben
  • Für Debug-Klassen, die Zugriff auf Interna wünschen, mache ich die Methode normalerweise privat und greife über Reflection darauf zu. Geschwindigkeit ist hier normalerweise nicht wichtig
  • Manchmal implementieren Sie eine Methode, die ein "Hack" ist oder auf andere Weise Änderungen unterworfen ist. Ich mache es öffentlich, aber benutze @Deprecated, um anzuzeigen, dass Sie sich nicht auf diese vorhandene Methode verlassen sollten.

Und schließlich, wenn es wirklich notwendig ist, gibt es das in den anderen Antworten erwähnte Friend-Accessor-Muster.


0

Kein Schlüsselwort oder so verwenden.

Sie könnten mit Reflexion usw. "betrügen", aber ich würde "betrügen" nicht empfehlen.


3
Ich würde dies für eine so schlechte Idee halten, dass es mir abscheulich ist, sie sogar vorzuschlagen. Offensichtlich ist dies bestenfalls ein Cludge und sollte nicht Teil eines Designs sein.
Shsteimer

0

Eine Methode, die ich zur Lösung dieses Problems gefunden habe, besteht darin, ein Accessor-Objekt wie folgt zu erstellen:

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

Der erste Code, der getAccessor()"Claims Ownership" des Accessors aufruft. Normalerweise ist dies Code, der das Objekt erstellt.

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

Dies hat auch einen Vorteil gegenüber dem Freundschaftsmechanismus von C ++, da Sie den Zugriff auf Instanzebene und nicht auf Klassenebene beschränken können . Durch Steuern der Zugriffsreferenz steuern Sie den Zugriff auf das Objekt. Sie können auch mehrere Accessoren erstellen und jedem einen unterschiedlichen Zugriff gewähren, wodurch genau gesteuert werden kann, welcher Code auf was zugreifen kann:

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

Wenn Sie möchten, dass die Dinge etwas besser organisiert sind, können Sie ein Referenzobjekt erstellen, das alles zusammenhält. Auf diese Weise können Sie alle Accessoren mit einem Methodenaufruf beanspruchen und sie zusammen mit ihrer verknüpften Instanz aufbewahren. Sobald Sie die Referenz haben, können Sie die Accessoren an den Code weitergeben, der sie benötigt:

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

Nach vielem Headbangen (nicht die gute Art) war dies meine endgültige Lösung und ich mag es sehr. Es ist flexibel, einfach zu bedienen und ermöglicht eine sehr gute Kontrolle über den Klassenzugriff. (Der Zugriff nur mit Referenz ist sehr nützlich.) Wenn Sie für die Accessoren / Referenzen geschützt statt privat verwenden, können Unterklassen von Foo sogar erweiterte Referenzen von zurückgeben getReference. Es erfordert auch keine Reflexion, so dass es in jeder Umgebung verwendet werden kann.


0

Ab Java 9 können Module verwendet werden, um dies in vielen Fällen zu einem Problem zu machen.


0

Ich bevorzuge Delegation oder Komposition oder Fabrikklasse (abhängig von dem Problem, das zu diesem Problem führt), um zu vermeiden, dass es eine öffentliche Klasse wird.

Wenn es sich um ein Problem mit "Schnittstellen- / Implementierungsklassen in verschiedenen Paketen" handelt, würde ich eine öffentliche Factory-Klasse verwenden, die sich im selben Paket wie das impl-Paket befindet, und die Offenlegung der impl-Klasse verhindern.

Wenn es sich um ein Problem "Ich hasse es, diese Klasse / Methode öffentlich zu machen, nur um diese Funktionalität für eine andere Klasse in einem anderen Paket bereitzustellen" handelt, würde ich eine öffentliche Delegatenklasse im selben Paket verwenden und nur diesen Teil der Funktionalität verfügbar machen benötigt von der "Außenseiter" Klasse.

Einige dieser Entscheidungen hängen von der Klassenladearchitektur des Zielservers (OSGi-Bundle, WAR / EAR usw.), der Bereitstellung und den Konventionen für die Paketbenennung ab. Zum Beispiel ist das oben vorgeschlagene Lösungsmuster "Friend Accessor" für normale Java-Anwendungen clever. Ich frage mich, ob es aufgrund des unterschiedlichen Klassenladestils schwierig wird, es in OSGi zu implementieren.


0

Ich weiß nicht, ob es für irgendjemanden von Nutzen ist, aber ich habe es folgendermaßen gehandhabt:

Ich habe eine Schnittstelle (AdminRights) erstellt.

Jede Klasse, die diese Funktionen aufrufen kann, sollte AdminRights implementieren.

Dann habe ich eine Funktion HasAdminRights wie folgt erstellt:

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

-1

Ich habe einmal eine reflexionsbasierte Lösung gesehen, die zur Laufzeit eine "Freundprüfung" mit Reflektion durchführte und den Aufrufstapel überprüfte, um festzustellen, ob die Klasse, die die Methode aufruft, dazu berechtigt war. Als Laufzeitprüfung hat es den offensichtlichen Nachteil.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.