답변:
다음은 JAVA에서 C ++ 친구 메커니즘을 복제하는 데 사용하는 작은 트릭입니다.
클래스 Romeo
와 다른 클래스 가 있다고 가정 해 봅시다 Juliet
. 증오 때문에 다른 패키지 (패밀리)에 있습니다.
Romeo
싶어 cuddle
Juliet
하고 Juliet
만하자 싶어 Romeo
cuddle
그녀를.
C ++에서는 (애인)으로 Juliet
선언 하지만 Java에는 그러한 것이 없습니다.Romeo
friend
수업과 요령은 다음과 같습니다.
여성분 먼저 :
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?");
}
}
따라서 방법 Juliet.cuddle
은 public
있지만 Romeo.Love
호출해야합니다. 그것은이를 사용하는 Romeo.Love
경우에만 보장하기 위해 "서명 보안"으로 Romeo
이 방법과 사랑이 진짜 너무 런타임이를 던질 것입니다 것을 확인 호출 할 수 있습니다 NullPointerException
이 경우를 null
.
이제 소년들 :
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);
}
}
클래스 Romeo.Love
는 public이지만 생성자는 private
입니다. 따라서 누구나 볼 수 있지만 Romeo
구성 할 수 있습니다. 정적 참조를 사용하므로 Romeo.Love
결코 사용되지 않는 것은 한 번만 구성되며 최적화에 영향을 미치지 않습니다.
따라서,이 Romeo
수 cuddle
Juliet
만 그 때문에 단지 그는 구성하고 액세스 할 수 있습니다 Romeo.Love
에 필요한 인스턴스 Juliet
에 cuddle
그녀 (또는 다른 그녀가 당신을 때릴 것을 NullPointerException
).
Romeo
'들 Love
에 대한 Julia
변화에 의해 영원한 love
으로 필드를 final
;-).
Java 디자이너는 C ++에서 작동하는 친구라는 개념을 명시 적으로 거부했습니다. "친구"를 같은 패키지에 넣습니다. 개인, 보호 및 패키지 보안은 언어 디자인의 일부로 시행됩니다.
James Gosling은 Java가 실수없이 C ++이되기를 원했습니다. 나는 친구가 OOP 원칙을 위반하기 때문에 실수라고 생각했다. 패키지는 OOP에 대해 너무 순수하지 않고 구성 요소를 구성 할 수있는 합리적인 방법을 제공합니다.
NR은 리플렉션을 사용하여 부정 행위를 할 수 있다고 지적했지만 SecurityManager를 사용하지 않는 경우에만 작동합니다. Java 표준 보안을 설정하면 보안 정책을 작성하여 명시 적으로 허용하지 않는 한 리플렉션을 사용하여 부정 행위를 할 수 없습니다.
friend
OOP 위반 (특히, 패키지 액세스보다 많은 것)을 실제로 생각했다면, 실제로 OOP를 이해하지 못했을 것 입니다.
'friend'개념은 예를 들어, API를 구현에서 분리하는 데 Java에서 유용합니다. 구현 클래스는 일반적으로 API 클래스 내부에 액세스해야하지만 API 클라이언트에 노출되어서는 안됩니다. 아래에 자세히 설명 된 'Friend Accessor'패턴을 사용하여이를 달성 할 수 있습니다.
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();
}
}
}
'친구'기능을 제공하는 클래스 :
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);
}
'friend'구현 패키지의 클래스에서 액세스하는 예 :
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
재사용 가능한 Friend
클래스 가있는 명확한 사용 사례가 있습니다. 이 메커니즘의 장점은 사용이 간편하다는 것입니다. 다른 응용 프로그램보다 유닛 테스트 클래스에 더 많은 액세스 권한을 부여하는 데 좋습니다.
시작하려면 다음은 Friend
클래스 사용 방법의 예입니다 .
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;
}
}
그런 다음 다른 패키지에서 다음을 수행 할 수 있습니다.
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
Friend
다음과 같이 클래스입니다.
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);
}
}
그러나 문제는 다음과 같이 악용 될 수 있다는 것입니다.
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
이제 Other
클래스에 공개 생성자가 없으므로 위의 Abuser
코드를 불가능하게 만드는 것이 사실 일 수 있습니다 . 그러나 클래스 에 공용 생성자 가 있으면 Friend 클래스를 내부 클래스로 복제하는 것이 좋습니다. 이 Other2
수업을 예로 들어 보자.
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);
}
}
그리고 Owner2
수업은 다음과 같습니다.
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
것을 알 수 Other2.Friend
클래스 따라서이 그 일을 훨씬 더 안전하게 만들고, 개인 생성자가 있습니다.
제공된 솔루션은 아마도 가장 단순하지 않았습니다. 다른 접근 방식은 C ++에서와 동일한 아이디어를 기반으로합니다. 소유자가 자신을 친구로 만드는 특정 클래스를 제외하고 개인 구성원은 패키지 / 개인 범위 외부에서 액세스 할 수 없습니다.
멤버에 대한 친구 액세스가 필요한 클래스는 액세스 구현 메소드를 구현하는 서브 클래스를 리턴하여 숨겨진 특성을 소유하는 클래스가 액세스를 내보낼 수있는 내부 공개 추상 "프렌즈 클래스"를 작성해야합니다. 친구 클래스의 "API"메소드는 개인용 일 수 있으므로 친구 액세스가 필요한 클래스 외부에서는 액세스 할 수 없습니다. 유일한 설명은 내보내기 클래스가 구현하는 추상 보호 멤버에 대한 호출입니다.
코드는 다음과 같습니다.
먼저 이것이 실제로 작동하는지 확인하는 테스트 :
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());
}
}
그런 다음 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();
}
}
마지막으로, 클래스 application.service.Service에만 패키지 개인 멤버에 대한 친숙한 액세스를 제공하는 Entity 클래스.
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();
}
};
}
}
그래, "friend service :: Service;"보다 약간 길다는 것을 인정해야한다 그러나 주석을 사용하여 컴파일 타임 검사를 유지하면서 단축시킬 수 있습니다.
Java에서는 "패키지 관련 친구"가있을 수 있습니다. 이것은 단위 테스트에 유용 할 수 있습니다. 메소드 앞에 private / public / protected를 지정하지 않으면 "패키지의 친구"가됩니다. 동일한 패키지의 클래스는 액세스 할 수 있지만 클래스 외부에서는 비공개입니다.
이 규칙은 항상 알려진 것은 아니며 C ++ "friend"키워드에 대한 근사치입니다. 나는 그것을 대체하는 것이 좋습니다.
C ++의 친구 클래스는 Java의 내부 클래스 개념과 같다고 생각합니다. 내부 클래스를 사용하면 실제로 클래스를 둘러싸고 클래스를 정의 할 수 있습니다. 동봉 된 수업은 동봉하는 수업의 공개 및 비공개 회원에게 완전히 액세스 할 수 있습니다. 다음 링크를 참조하십시오 : http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
친구 접근 자 패턴을 사용하는 방법이 너무 복잡하다고 생각합니다. 나는 같은 문제에 직면해야했고 Java에서 C ++로 알려진 좋은 오래된 복사 생성자를 사용하여 해결했습니다.
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;
}
}
응용 프로그램에서 다음 코드를 작성할 수 있습니다.
public class MyApp {
private static class ProtectedAccessor extends ProtectedContainer {
protected ProtectedAccessor() {
super();
}
protected PrivateAccessor(ProtectedContainer prot) {
super(prot);
}
public String exposeProtected() {
return iwantAccess;
}
}
}
이 방법의 장점은 응용 프로그램 만 보호 된 데이터에 액세스 할 수 있다는 것입니다. 친구 키워드를 정확하게 대체하는 것은 아닙니다. 그러나 사용자 정의 라이브러리를 작성하고 보호 된 데이터에 액세스해야 할 때 매우 적합하다고 생각합니다.
ProtectedContainer 인스턴스를 처리해야 할 때마다 ProtectedAccessor를 감싸서 액세스 할 수 있습니다.
또한 보호 된 방법으로 작동합니다. API에서 보호되도록 정의하십시오. 나중에 응용 프로그램에서 개인 래퍼 클래스를 작성하고 보호 된 메서드를 공개로 노출합니다. 그게 다야.
ProtectedContainer
패키지 외부에서 서브 클래 싱 할 수 있습니다!
대부분의 경우 친구 키워드가 불필요하다는 데 동의합니다.
마지막으로 실제로 필요한 경우 다른 답변에 언급 된 친구 접근 자 패턴이 있습니다.
키워드 등을 사용하지 않습니다.
리플렉션 등을 사용하여 "속임수"를 사용할 수 있지만 "속임수"는 권장하지 않습니다.
이 문제를 해결하기 위해 찾은 방법은 다음과 같이 접근 자 객체를 만드는 것입니다.
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()
"소유권을 주장합니다". 일반적으로 이것은 객체를 생성하는 코드입니다.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
또한 클래스 별 수준이 아니라 인스턴스 별 수준에서 액세스를 제한 할 수 있기 때문에 C ++의 친구 메커니즘보다 이점이 있습니다 . 접근 자 참조를 제어하여 객체에 대한 액세스를 제어합니다. 또한 여러 접근자를 만들고 각각에 다른 액세스 권한을 부여하여 어떤 코드가 무엇에 액세스 할 수 있는지를 세밀하게 제어 할 수 있습니다.
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();
}
}
마지막으로, 좀 더 체계적으로 정리하고 싶다면 모든 것을 하나로 묶는 참조 객체를 만들 수 있습니다. 이를 통해 한 번의 메서드 호출로 모든 접근자를 요구할 수 있으며 연결된 인스턴스와 함께 유지할 수도 있습니다. 참조가 있으면 접근자를 참조가 필요한 코드로 전달할 수 있습니다.
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();
}
}
많은 종류의 헤드 뱅잉 (좋은 종류는 아님) 후에, 이것이 나의 최종 해결책이었고, 나는 그것을 매우 좋아했습니다. 유연하고 사용하기 쉬우 며 클래스 액세스를 매우 잘 제어 할 수 있습니다. (는 참조 만 접근이 매우 유용합니다.) 대신도에서 확장 된 참조를 반환 할 수 있습니다 푸의 접근 / 참조, 하위 클래스에 대한 개인의 보호를 사용하는 경우 getReference
. 또한 반사가 필요하지 않으므로 모든 환경에서 사용할 수 있습니다.
나는 공개 클래스로 만들지 않기 위해 위임 또는 구성 또는 팩토리 클래스 (이 문제를 일으키는 문제에 따라)를 선호합니다.
"다른 패키지의 인터페이스 / 구현 클래스"문제인 경우 impl 패키지와 동일한 패키지에있는 공용 팩토리 클래스를 사용하고 impl 클래스의 노출을 방지합니다.
"다른 클래스의 다른 클래스에이 기능을 제공하기 위해이 클래스 / 메소드를 공개하기 싫어"문제인 경우, 동일한 패키지에서 공용 델리게이트 클래스를 사용하고 해당 기능의 해당 부분 만 노출합니다 "outsider"클래스에 필요합니다.
이러한 결정 중 일부는 대상 서버 클래스 로딩 아키텍처 (OSGi 번들, WAR / EAR 등), 배포 및 패키지 명명 규칙에 의해 결정됩니다. 예를 들어, 위의 제안 된 솔루션 인 'Friend Accessor'패턴은 일반적인 Java 애플리케이션에 적합합니다. 클래스로드 스타일의 차이로 인해 OSGi에서 구현하기 까다로운 지 궁금합니다.
누구에게나 쓸모가 있는지 모르겠지만 다음과 같이 처리했습니다.
인터페이스 (AdminRights)를 만들었습니다.
해당 함수를 호출 할 수 있어야하는 모든 클래스는 AdminRights를 구현해야합니다.
그런 다음 HasAdminRights 함수를 다음과 같이 만들었습니다.
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;
}
}