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?
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?
Odpowiedzi:
Oto mała sztuczka, której używam w JAVA do replikacji mechanizmu przyjaciela C ++.
Powiedzmy, że mam klasę Romeo
i inną klasę Juliet
. Są w różnych opakowaniach (rodzinnych) z powodów nienawiści.
Romeo
chce cuddle
Juliet
i Juliet
chce tylko Romeo
cuddle
jej pozwolić .
W C ++ Juliet
zadeklarowałbym Romeo
jako (kochanek), friend
ale 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.cuddle
jest taka, public
ale trzeba Romeo.Love
ją nazwać. Używa tego Romeo.Love
jako „zabezpieczenia podpisu”, aby upewnić się, że Romeo
może wywołać tę metodę i sprawdza, czy miłość jest prawdziwa, aby środowisko wykonawcze wyrzuciło, NullPointerException
jeś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.Love
jest publiczna, ale jej konstruktorem jest private
. Dlatego każdy może to zobaczyć, ale tylko Romeo
go zbudować. Używam statycznego odniesienia, więc ten, Romeo.Love
który nigdy nie jest używany, jest budowany tylko raz i nie wpływa na optymalizację.
Dlatego Romeo
może cuddle
Juliet
i tylko on może, ponieważ tylko on może zbudować Romeo.Love
instancję i uzyskać do niej dostęp , co jest Juliet
dla cuddle
niej wymagane (w przeciwnym razie da ci klapsa NullPointerException
).
Romeo
„s Love
do Julia
wiecznego poprzez zmianę love
pola będzie final
;-).
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ą.
friend
naruszył 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).
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);
}
}
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.
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ę
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ć.
Oto wyraźny przykład użycia z Friend
klasą 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 Friend
klasy.
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);
}
}
Friend
Klasa 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 Other
klasa nie ma żadnych publicznych konstruktorów, przez co powyższy Abuser
kod 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ę Other2
klasę 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 Owner2
klasa 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.Friend
klasa ma prywatnego konstruktora, dzięki czemu jest to o wiele bezpieczniejszy sposób.
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.
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.
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
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.
ProtectedContainer
można go sklasyfikować poza pakietem!
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.
Zgadzam się, że w większości przypadków słowo kluczowe znajomego jest niepotrzebne.
I wreszcie, jeśli to naprawdę konieczne, istnieje wzorzec akcesorium przyjaciela wymieniony w innych odpowiedziach.
Nie używasz słowa kluczowego lub czegoś podobnego.
Możesz „oszukiwać” za pomocą refleksji itp., Ale nie polecam „oszukiwania”.
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.
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.
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;
}
}