Diğer sınıfın bir alt sınıfı yapmak zorunda kalmadan başka bir pakette bir sınıfın ortak olmayan yöntemlerine erişebilen bir pakette bir Java sınıfı yazabilmek istiyorum. Mümkün mü?
Diğer sınıfın bir alt sınıfı yapmak zorunda kalmadan başka bir pakette bir sınıfın ortak olmayan yöntemlerine erişebilen bir pakette bir Java sınıfı yazabilmek istiyorum. Mümkün mü?
Yanıtlar:
İşte C ++ arkadaş mekanizmasını çoğaltmak için JAVA kullandığım küçük bir hile.
Diyelim ki bir sınıfım Romeo
ve başka bir sınıfım var Juliet
. Nefret nedenleriyle farklı paketler (aile) içindeler.
Romeo
istediği cuddle
Juliet
ve Juliet
sadece izin istiyor Romeo
cuddle
onu.
C ++ ' da (sevgili) Juliet
ilan eder Romeo
, friend
ancak java'da böyle bir şey yoktur.
İşte sınıflar ve püf noktası:
Önce bayanlar :
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?");
}
}
Yani yöntem Juliet.cuddle
, public
ama Romeo.Love
onu çağırmak için a'ya ihtiyacınız var. Bu kullanır Romeo.Love
sadece emin olmak için bir "imza güvenlik" olarak Romeo
bu yöntemi ve sevgi gerçek böylece çalışma zamanı bir atmak olacağını olduğuna kontrolleri çağırabilir NullPointerException
Eğer öyleyse null
.
Şimdi çocuklar:
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);
}
}
Sınıf Romeo.Love
herkese açık, ancak kurucusu private
. Bu nedenle herkes onu görebilir, ancak sadece Romeo
inşa edebilir. Ben Romeo.Love
asla kullanılmayan sadece bir kez inşa ve optimizasyonu etkilemez böylece statik bir başvuru kullanın .
Bu nedenle, Romeo
can cuddle
Juliet
ve sadece kendisinin çünkü sadece o bir inşa ve erişebilir edebilir Romeo.Love
gereklidir örneği, Juliet
için cuddle
ona (veya başka o size tokat edeceğiz NullPointerException
).
Romeo
's Love
için Julia
değiştirerek sonsuz love
olmasını alanını final
;-).
Java tasarımcıları C ++ 'da çalışırken arkadaş fikrini açıkça reddetti. "Arkadaşlarınızı" aynı pakete koydunuz. Özel, korumalı ve paketlenmiş güvenlik, dil tasarımının bir parçası olarak uygulanır.
James Gosling Java'nın hatasız C ++ olmasını istedi. Arkadaşının bir hata olduğunu düşündüğüne inanıyorum çünkü OOP ilkelerini ihlal ediyor. Paketler, OOP konusunda fazla saf olmadan bileşenleri organize etmek için makul bir yol sağlar.
NR, yansıma kullanarak hile yapabileceğinizi belirtti, ancak bu bile yalnızca SecurityManager'ı kullanmıyorsanız işe yarar. Java standart güvenliğini açarsanız, özellikle izin vermek için güvenlik ilkesi yazmadığınız sürece yansıma ile hile yapamazsınız.
friend
ihlal ettiğini düşündüyse (özellikle paket erişiminden daha fazlası) gerçekten anlamadı (tamamen mümkün, birçok kişi yanlış anladı).
'Arkadaş' kavramı Java'da, örneğin bir API'nın uygulanmasından ayrılması için kullanışlıdır. Uygulama sınıflarının API sınıfı iç öğelerine erişmesi yaygındır, ancak bunlar API istemcilerine maruz bırakılmamalıdır. Bu, aşağıda ayrıntıları verilen 'Friend Accessor' kalıbı kullanılarak gerçekleştirilebilir:
API aracılığıyla maruz kalan sınıf:
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();
}
}
}
'Arkadaş' işlevselliğini sağlayan sınıf:
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);
}
'Arkadaş' uygulama paketindeki bir sınıftan örnek erişim:
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
Sorunuza, tüm sınıfları aynı pakette tutmayı içermeyen iki çözüm var.
Birincisi , (Pratik API Tasarımı, Tulach 2008) 'de açıklanan Arkadaş Erişimcisi / Arkadaş Paketi modelini kullanmaktır .
İkincisi OSGi kullanmaktır. Burada OSGi'nin bunu nasıl başardığını açıklayan bir makale var .
Bildiğim kadarıyla mümkün değil.
Belki, bize tasarımınız hakkında daha fazla bilgi verebilirsin. Bu gibi sorular muhtemelen tasarım kusurlarının sonucudur.
Sadece düşün
eirikma'nın cevabı kolay ve mükemmeldir. Bir şey daha ekleyebilirim: halka açık bir yöntem yerine, kullanılamayacak bir arkadaş edinmek için getFriend () yerine, bir adım daha ileri gidebilir ve bir belirteç olmadan arkadaş almayı engelleyebilirsiniz: getFriend (Service.FriendToken). Bu FriendToken, özel bir kurucuya sahip bir iç kamu sınıfı olacaktır, böylece yalnızca Hizmet bir örneği başlatabilirdi.
Yeniden kullanılabilir bir Friend
sınıf içeren açık bir kullanım örneği örneği . Bu mekanizmanın yararı kullanım kolaylığıdır. Birim test sınıflarına uygulamanın geri kalanından daha fazla erişim vermek için iyi olabilir.
Başlamak için, Friend
sınıfın .
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;
}
}
Sonra başka bir pakette şunları yapabilirsiniz:
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
Friend
Şöyle sınıftır.
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);
}
}
Ancak, sorun şu şekilde kötüye kullanılmasıdır:
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
Şimdi, Other
sınıfın herhangi bir kamu kurucuya sahip olmadığı doğru olabilir , bu nedenle yukarıdaki Abuser
kodu imkansız hale getirir . Sınıf Ancak, does bir public kurucu sahip o zaman bir iç sınıf olarak Arkadaş sınıfını çoğaltmak için muhtemelen tavsiye edilir. Bu Other2
sınıfı örnek olarak alın :
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);
}
}
Ve sonra Owner2
sınıf şöyle olurdu:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Other2.Friend
Sınıfın özel bir kurucuya sahip olduğuna dikkat edin , böylece bunu daha güvenli bir yol haline getirin.
Sağlanan çözüm belki de en basit değildi. Başka bir yaklaşım, C ++ ile aynı fikre dayanmaktadır: özel üyelerin, sahibinin kendisinin bir arkadaşı yaptığı belirli bir sınıf dışında, paket / özel kapsam dışında erişilemez.
Bir üyeye arkadaş erişimine ihtiyaç duyan sınıf, gizli özelliklere sahip sınıfın, erişim uygulama yöntemlerini uygulayan bir alt sınıf döndürerek erişimi dışa aktarabileceği bir iç genel soyut "arkadaş sınıfı" oluşturmalıdır. Arkadaş sınıfının "API" yöntemi özel olabilir, bu nedenle arkadaş erişimi gerektiren sınıfın dışında erişilebilir değildir. Tek ifadesi, ihracatçı sınıfın uyguladığı soyut korumalı bir üyeye yapılan çağrıdır.
İşte kod:
İlk olarak bunun gerçekten işe yaradığını doğrulayan test:
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());
}
}
Ardından, Varlık paket özel üyesine arkadaş erişimi gerektiren Hizmet:
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();
}
}
Son olarak: paket özel üyesine yalnızca sınıf application.service.Service sınıfına kolay erişim sağlayan Entity sınıfı.
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();
}
};
}
}
Tamam, itiraf etmeliyim ki "friend service :: Service" den biraz daha uzun; ancak ek açıklamaları kullanarak derleme zamanı denetimini sürdürürken kısaltmak mümkün olabilir.
Java'da "paketle ilgili bir dostluğa" sahip olmak mümkündür. Bu birim testi için yararlı olabilir. Bir yöntemin önünde özel / genel / korumalı belirtmezseniz, "paketteki arkadaş" olur. Aynı paketteki bir sınıf bu pakete erişebilir, ancak sınıf dışında özel olacaktır.
Bu kural her zaman bilinmemektedir ve bir C ++ "arkadaş" anahtar kelimesi için iyi bir yaklaşımdır. İyi bir yedek buldum.
C ++ arkadaş sınıfları Java iç sınıf kavramı gibi olduğunu düşünüyorum. İç sınıfları kullanarak aslında bir kapalı sınıf ve bir kapalı sınıf tanımlayabilirsiniz. Kapalı sınıf, kapalı sınıfın genel ve özel üyelerine tam erişime sahiptir. aşağıdaki bağlantıya bakın: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Arkadaş erişim düzenini kullanma yaklaşımı çok karmaşık. Aynı sorunla yüzleşmek zorunda kaldım ve Java'da C ++ 'dan bilinen iyi, eski kopya oluşturucuyu kullanarak çözdüm:
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;
}
}
Başvurunuzda aşağıdaki kodu yazabilirsiniz:
public class MyApp {
private static class ProtectedAccessor extends ProtectedContainer {
protected ProtectedAccessor() {
super();
}
protected PrivateAccessor(ProtectedContainer prot) {
super(prot);
}
public String exposeProtected() {
return iwantAccess;
}
}
}
Bu yöntemin avantajı, yalnızca uygulamanızın korunan verilere erişimi olmasıdır. Bu, tam olarak arkadaş anahtar kelimesinin yerine geçmez. Ancak, özel kütüphaneler yazdığınızda ve korumalı verilere erişmeniz gerektiğinde oldukça uygun olduğunu düşünüyorum.
ProtectedContainer örnekleri ile uğraşmanız gerektiğinde, ProtectedAccessor'unuzu etrafına sarabilirsiniz ve erişim elde edersiniz.
Ayrıca korumalı yöntemlerle de çalışır. Bunları API'nızda korumalı olarak tanımlarsınız. Daha sonra uygulamanızda özel bir sarmalayıcı sınıfı yazar ve korunan yöntemi herkese açık olarak ortaya koyarsınız. Bu kadar.
ProtectedContainer
paketin dışında alt sınıflara ayrılabilir!
Korumalı yöntemlere erişmek istiyorsanız, kullanmak istediğiniz sınıfın herkese açık (veya daha güvenli olması için ad alanına dahili olarak) kullanmak istediğiniz yöntemleri gösteren ve sınıfınızda bu sınıfın bir örneğini içeren bir alt sınıf oluşturabilirsiniz. (proxy olarak kullanın).
Özel yöntemler söz konusu olduğunda (bence) şansınız kalmadı.
Çoğu durumda arkadaş anahtar kelimesinin gereksiz olduğunu kabul ediyorum.
Ve son olarak, gerçekten gerekliyse, diğer cevaplarda bahsedilen arkadaş erişimci modeli var.
Bu sorunu çözmek için bulduğum bir yöntem şöyle bir erişimci nesnesi oluşturmaktır:
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();
}
}
getAccessor()
" Kod sahibi" olarak adlandırılan ilk kod erişimcinin sahipliğini talep eder. Genellikle, nesneyi oluşturan kod budur.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
Bu, C ++ 'ın arkadaş mekanizmasına göre bir avantaja sahiptir, çünkü sınıf başına bir seviyenin aksine, örnek başına düzeyde erişimi sınırlamanıza izin verir . Erişimci referansını denetleyerek, nesneye erişimi denetlersiniz. Ayrıca, birden çok erişimci oluşturabilir ve her birine farklı erişim verebilirsiniz; bu, hangi kodun neye erişebileceği üzerinde hassas bir denetime izin verir:
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();
}
}
Son olarak, işlerin biraz daha organize olmasını istiyorsanız, her şeyi bir arada tutan bir referans nesne oluşturabilirsiniz. Bu, tüm erişimcileri bir yöntem çağrısıyla talep etmenizi ve bağlantılı örnekleriyle birlikte tutmanızı sağlar. Referansı aldıktan sonra, erişimcileri ihtiyacı olan koda iletebilirsiniz:
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();
}
}
Çok kafa vurduktan sonra (iyi değil), bu benim son çözümümdü ve çok beğendim. Esnek, kullanımı basittir ve sınıf erişimi üzerinde çok iyi kontrol sağlar. ( Yalnızca başvuru ile erişimi çok kullanışlıdır.) Erişimciler / referanslar için özel yerine korumalı kullanırsanız, Foo'nun alt sınıfları genişletilmiş referansları bile döndürebilir getReference
. Ayrıca herhangi bir yansıma gerektirmez, bu nedenle herhangi bir ortamda kullanılabilir.
Ben genel bir sınıf yapmaktan kaçınmak için (bu sorunla sonuçlanan soruna bağlı olarak) temsilci seçme veya kompozisyon veya fabrika sınıfı tercih ederim.
"Farklı paketlerdeki arabirim / uygulama sınıfları" sorunu ise, o zaman impl paketi ile aynı pakette olacak ve impl sınıfının maruz kalmasını önleyen bir genel fabrika sınıfı kullanırdım.
"Farklı bir pakette başka bir sınıf için bu işlevselliği sağlamak için bu sınıf / yöntem genel yapmaktan nefret ediyorum" sorun varsa, o zaman aynı pakette ortak bir delege sınıf kullanır ve işlevsellik yalnızca o kısmını ortaya "yabancı" sınıfın ihtiyacı.
Bu kararlardan bazıları, hedef sunucu sınıf yükleme mimarisi (OSGi paketi, WAR / EAR vb.), Dağıtım ve paket adlandırma kuralları tarafından yönlendirilir. Örneğin, yukarıda önerilen çözüm olan 'Friend Accessor' deseni normal java uygulamaları için akıllıdır. Sınıf yükleme stilindeki fark nedeniyle OSGi'de uygulamak zorlaşıyor mu acaba?
Kimseye herhangi bir yarar olup olmadığını bilmiyorum ama ben şu şekilde ele:
Bir arayüz oluşturdum (AdminRights).
Bu işlevleri çağırabilmesi gereken her sınıfın AdminRights uygulaması gerekir.
Sonra HasAdminRights aşağıdaki gibi bir fonksiyon yarattım:
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;
}
}
Bir keresinde yansıma kullanarak ve yöntemi çağırmak sınıf bunu yapmak için izin verilmiş olup olmadığını görmek için çağrı yığını kontrol çalışma zamanında "arkadaş kontrol" yaptı yansıma tabanlı bir çözüm gördüm. Bir çalışma zamanı kontrolü olarak, belirgin bir dezavantajı vardır.