Java에서 C++ 'friend' 개념을 시뮬레이트할 수 있는 방법이 있나요?
다른 클래스의 서브클래스로 하지 않고 다른 패키지에 있는 클래스의 비공개 메서드에 액세스할 수 있는 Java 클래스를 하나의 패키지로 작성할 수 있도록 하고 싶습니다.이게 가능합니까?
다음은 JAVA에서 C++ 친구 메커니즘을 복제하기 위해 사용하는 작은 트릭입니다.
, 수업이 합시다.Romeo
클래스Juliet
그들은 증오를 이유로 서로 다른 꾸러미를 가지고 있다.
Romeo
싶다cuddle
Juliet
★★★★★★★★★★★★★★★★★」Juliet
놔두고 싶다Romeo
cuddle
여자그녀.그녀.그녀.
에서는 C++입니다.Juliet
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
이지만, 그 는 「」, 「」입니다.private
누구나 볼 수은 오직 '만 볼 수 있습니다.Romeo
만들 수 있습니다.를 사용하고 에, 「 」는 「 」입니다.Romeo.Love
사용되지 않는 것은 한 번만 구성되며 최적화에 영향을 주지 않습니다.
therefore그 、Romeo
수 있다cuddle
Juliet
그 접근할 수 있기 에 할 수 있다.Romeo.Love
""에 합니다.Juliet
로로 합니다.cuddle
않으면 그녀는이다)NullPointerException
를 참조해 주세요.
자바 디자이너들은 C++에서 작동하는 친구라는 아이디어를 명시적으로 거부했습니다.당신은 당신의 "친구들"을 같은 꾸러미에 담습니다.프라이빗, 보호 및 패키지 보안은 언어 설계의 일부로 적용됩니다.
James Gosling은 자바가 실수 없이 C++가 되기를 원했다.나는 그가 그 친구가 OOP 원칙에 위배되기 때문에 실수라고 느꼈다고 믿는다.패키지를 사용하면 OOP에 대해 지나치게 순수하게 생각하지 않고 컴포넌트를 구성할 수 있습니다.
NR은 리플렉션을 사용하여 부정행위를 할 수 있지만 Security Manager를 사용하지 않는 경우에만 부정행위를 할 수 있다고 지적했습니다.Java 표준 보안을 설정한 경우 보안 정책을 구체적으로 작성하지 않으면 반영으로 부정행위를 할 수 없습니다.
'friend' 개념은 Java에서 API를 구현에서 분리하는 데 유용합니다.구현 클래스에서 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);
}
}
모든 클래스를 같은 패키지로 유지하는 것이 아니라 두 가지 해결 방법이 있습니다.
첫 번째는 (Practical API Design, Tulach 2008)에 기재되어 있는 Friend Accessor/Friend Package 패턴을 사용하는 것입니다.
두 번째는 OSGi를 사용하는 것입니다.OSGi가 이를 실현하는 방법을 설명하는 기사가 여기 있습니다.
내가 아는 한 그것은 불가능하다.
아마도 당신의 디자인에 대해 좀 더 자세히 알려 주실 수 있을 겁니다.이러한 질문은 설계상의 결함으로 인해 발생할 수 있습니다.
생각해 보세요
- 이러한 클래스가 매우 밀접하게 관련되어 있는데 왜 다른 패키지에 포함되어 있습니까?
- A가 B의 프라이빗 멤버에 액세스 할 수 있는 권한을 가지고 있는가, 아니면 클래스 B로 이동하여 A에 의해 트리거해야 하는가?
- 이것이 정말 도움이 될까요, 아니면 이벤트 처리가 더 나은가요?
eirikma의 답은 쉽고 훌륭합니다.한 가지 더 덧붙이자면, getFriend()라는 공개적인 메서드를 사용하는 대신, 한 걸음 더 나아가 토큰 없이 getFriend(서비스)를 얻을 수 없도록 할 수 있습니다.FriendToken).이 친구토큰은 프라이빗 컨스트럭터가 있는 내부 퍼블릭클래스가 되므로 서비스만 인스턴스화할 수 있습니다.
재사용이 한 사용 .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
에 훨씬 할 수 있습니다.class 프 、 class에 、 class class에 class class class class。
제공된 솔루션은 아마도 가장 단순하지 않을 것이다.또 다른 접근법은 C++와 같은 아이디어에 기초하고 있습니다.프라이빗 멤버는 소유자가 자신의 친구를 만드는 특정 클래스를 제외하고 패키지/프라이빗 범위 밖에서는 접근할 수 없습니다.
멤버에 대한 친구 액세스가 필요한 클래스는 숨겨진 속성을 소유한 클래스가 액세스 구현 메서드를 구현하는 하위 클래스를 반환하여 액세스를 내보낼 수 있는 내부 퍼블릭 추상 "친구 클래스"를 만들어야 합니다.friend 클래스의 "API" 메서드는 비공개로 할 수 있으므로 friend 액세스가 필요한 클래스 외부에서 액세스할 수 없습니다.유일한 문은 내보내기 클래스가 구현하는 추상 보호 멤버에 대한 호출입니다.
코드는 다음과 같습니다.
먼저 이것이 실제로 기능하는 것을 검증하는 테스트입니다.
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());
}
}
그런 다음 엔티티의 패키지 개인 멤버에 대한 친구 액세스가 필요한 서비스:
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();
}
}
마지막으로 클래스 응용 프로그램에만 패키지 개인 멤버에 대한 액세스를 제공하는 엔티티 클래스입니다.서비스.서비스.
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();
}
};
}
}
좋아, 친구 서비스보다 좀 더 길다는 건 인정해야겠어.Service;"는 단, 주석을 사용하여 컴파일 시간 체크를 유지하면서 단축할 수 있습니다.
Java에서는 "패키지 관련 친구"를 가질 수 있습니다.이것은 유닛 테스트에 유용하게 사용할 수 있습니다.메서드 앞에 private/public/protected를 지정하지 않으면 "friend in the package"가 됩니다.같은 패키지의 클래스가 액세스 할 수 있지만, 클래스 이외에서는 프라이빗하게 액세스 할 수 있습니다.
이 규칙이 항상 알려진 것은 아니며 C++ "friend" 키워드의 근사치입니다.나는 그것이 좋은 대체품이라고 생각한다.
C++의 친구 수업은 자바에서 이너클래스 개념이라고 생각합니다.inner-class를 사용하면 실제로 둘러싸인 클래스와 둘러싸인 클래스를 정의할 수 있습니다.폐쇄 클래스는 폐쇄 클래스의 공용 및 개인 구성원에 대한 전체 액세스 권한을 가집니다.다음 링크를 참조하십시오.http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
키워드를 사용하지 않는 등.
성찰 등을 이용해 '사기'를 할 수는 있지만, 저는 '사기'를 추천하지 않습니다.
친구 액세스 패턴을 사용하는 방법은 너무 복잡하다고 생각합니다.나는 같은 문제에 직면해야 했고, 자바에서 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;
}
}
}
이 방법의 장점은 응용 프로그램만 보호된 데이터에 액세스할 수 있다는 것입니다.friend 키워드를 대체한 것은 아닙니다.하지만 커스텀 라이브러리를 작성하고 보호된 데이터에 액세스해야 할 때 매우 적합합니다.
ProtectedContainer 인스턴스를 처리해야 할 때마다 ProtectedAccessor를 감싸서 액세스할 수 있습니다.
보호된 메서드에서도 작동합니다.API에서 보호되고 있는 것을 정의합니다.나중에 응용 프로그램에서 개인 래퍼 클래스를 작성하고 보호된 메서드를 공개합니다.바로 그겁니다.
보호된 메서드에 액세스하려면 사용할 클래스의 하위 클래스를 생성하여 퍼블릭(또는 더 안전하기 위해 네임스페이스 내부)으로 사용할 메서드를 노출하고 해당 클래스의 인스턴스를 클래스에 포함할 수 있습니다(프록시로 사용).
사적인 방법에 관한 한 너는 운이 없다고 생각한다.
대부분의 경우 friend 키워드가 불필요하다는 것에 동의합니다.
- 클래스가 많이 얽혀 있는 경우 대부분의 경우 패키지 개인(기본값)으로 충분합니다.
- 내부로의 액세스가 필요한 디버깅클래스의 경우 보통 메서드를 비공개로 하고 리플렉션을 통해 접근합니다.여기서는 보통 속도가 중요하지 않습니다.
- 경우에 따라서는 "해킹" 방식이나 변경될 수 있는 방법을 구현할 수 있습니다.공개하고 있습니다만, @Depreced 를 사용해, 이 메서드에 의존하지 말아 주세요.
마지막으로 꼭 필요한 경우 다른 답변에서 언급한 친구 접근자 패턴이 있습니다.
이 문제를 해결하기 위한 방법은 다음과 같은 접근자 개체를 만드는 것입니다.
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();
}
}
(좋은 것은 아니지만) 여러 번 머리를 싸매고 나서, 이것이 나의 최종 해결책이었고, 나는 그것을 매우 좋아한다.유연하고 사용하기 쉬우며 클래스 접근을 매우 잘 제어할 수 있습니다.(참조 전용 접근은 매우 편리합니다).접근자/참조용으로 private가 아닌 protected를 사용하면 Foo의 서브클래스는 에서 확장된 참조를 반환할 수도 있습니다.getReference
반사도 필요 없기 때문에 어떤 환경에서도 사용할 수 있습니다.
Java 9에서는 모듈을 사용하여 많은 경우 이 문제를 해결할 수 있습니다.
퍼블릭 클래스가 되지 않도록 위임 또는 구성 또는 팩토리 클래스(이 문제의 원인이 되는 문제에 따라 다름)를 선호합니다.
"다른 패키지의 인터페이스/실장 클래스" 문제인 경우, 저는 inclash 패키지와 동일한 패키지에 포함된 퍼블릭팩토리 클래스를 사용하여 inclash 클래스의 노출을 방지합니다.
"다른 패키지의 다른 클래스에 이 기능을 제공하기 위해 이 클래스/메서드를 공개하고 싶지 않다"는 문제일 경우 동일한 패키지로 퍼블릭 위임 클래스를 사용하여 "외부" 클래스에서 필요한 기능 중 일부만 공개합니다.
이러한 결정의 일부는 타깃 서버 클래스 로드 아키텍처(OSGi 번들, WAR/EAR 등), 도입 및 패키지 명명 규칙에 따라 결정됩니다.예를 들어, 위에서 제안한 솔루션인 'Friend Accessor' 패턴은 일반 Java 애플리케이션에 적합합니다.classloading 스타일이 다르기 때문에 OSGi에서 구현하기가 까다롭지 않은지 궁금합니다.
한번은 리플렉션을 사용하여 런타임에 "친구 체크"를 하고 메서드를 호출하는 클래스가 그렇게 할 수 있는지 여부를 확인하는 리플렉션 기반 솔루션을 본 적이 있습니다.런타임 체크이기 때문에 명백한 단점이 있습니다.
언급URL : https://stackoverflow.com/questions/182278/is-there-a-way-to-simulate-the-c-friend-concept-in-java
'programing' 카테고리의 다른 글
스프링 경유 RESTful 인증 (0) | 2022.07.20 |
---|---|
OpenGL에서 glOrtho()를 사용하는 방법 (0) | 2022.07.20 |
2차원 어레이를 함수로 올바르게 변환하는 방법 (0) | 2022.07.20 |
문자열 리터럴:그들은 어디로 갑니까? (0) | 2022.07.17 |
Vuejs 단일 파일 컴포넌트를 컴파일하는 방법 (0) | 2022.07.17 |