[java] Java에서 C ++ ‘friend’개념을 시뮬레이션하는 방법이 있습니까?

한 패키지에 Java 클래스를 작성하여 다른 클래스의 서브 클래스로 만들 필요없이 다른 패키지에있는 클래스의 비공개 메소드에 액세스 할 수 있기를 원합니다. 이게 가능해?



답변

다음은 JAVA에서 C ++ 친구 메커니즘을 복제하는 데 사용하는 작은 트릭입니다.

클래스 Romeo와 다른 클래스 가 있다고 가정 해 봅시다 Juliet. 증오 때문에 다른 패키지 (패밀리)에 있습니다.

Romeo싶어 cuddle Juliet하고 Juliet만하자 싶어 Romeo cuddle그녀를.

C ++에서는 (애인)으로 Juliet선언 하지만 Java에는 그러한 것이 없습니다.Romeofriend

수업과 요령은 다음과 같습니다.

여성분 먼저 :

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.cuddlepublic있지만 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결코 사용되지 않는 것은 한 번만 구성되며 최적화에 영향을 미치지 않습니다.

따라서,이 Romeocuddle Juliet만 그 때문에 단지 그는 구성하고 액세스 할 수 있습니다 Romeo.Love에 필요한 인스턴스 Julietcuddle그녀 (또는 다른 그녀가 당신을 때릴 것을 NullPointerException).


답변

Java 디자이너는 C ++에서 작동하는 친구라는 개념을 명시 적으로 거부했습니다. “친구”를 같은 패키지에 넣습니다. 개인, 보호 및 패키지 보안은 언어 디자인의 일부로 시행됩니다.

James Gosling은 Java가 실수없이 C ++이되기를 원했습니다. 나는 친구가 OOP 원칙을 위반하기 때문에 실수라고 생각했다. 패키지는 OOP에 대해 너무 순수하지 않고 구성 요소를 구성 할 수있는 합리적인 방법을 제공합니다.

NR은 리플렉션을 사용하여 부정 행위를 할 수 있다고 지적했지만 SecurityManager를 사용하지 않는 경우에만 작동합니다. Java 표준 보안을 설정하면 보안 정책을 작성하여 명시 적으로 허용하지 않는 한 리플렉션을 사용하여 부정 행위를 할 수 없습니다.


답변

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


답변

귀하의 질문에 대한 모든 솔루션을 동일한 패키지에 유지하는 것을 포함하지 않는 두 가지 솔루션이 있습니다.

첫 번째는 (Practical API Design, Tulach 2008)에 설명 된 Friend Accessor / Friend Package 패턴 을 사용하는 것 입니다.

두 번째는 OSGi를 사용하는 것입니다. OSGi가이를 수행하는 방법을 설명하는 기사가 있습니다 .

관련 질문 : 1 , 23 .


답변

내가 아는 한 불가능합니다.

아마도, 당신은 당신의 디자인에 대한 좀 더 자세한 정보를 우리에게 줄 수 있습니다. 이와 같은 질문은 디자인 결함의 결과 일 수 있습니다.

그냥 고려

  • 클래스가 서로 밀접하게 관련되어있는 이유는 무엇입니까?
  • A가 B의 개인 멤버에 액세스해야합니까, 아니면 조작을 클래스 B로 이동시키고 A에 의해 트리거되어야합니까?
  • 이것이 실제로 전화를 걸거나 이벤트 처리가 더 낫습니까?

답변

eirikma의 대답은 쉽고 우수합니다. 공개적으로 접근 가능한 메소드 인 getFriend ()를 사용하여 친구를 사용할 수 없게하는 대신 한 단계 더 나아가서 토큰없이 친구를 얻지 못하게 할 수 있습니다 : getFriend (Service.FriendToken). 이 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클래스 따라서이 그 일을 훨씬 더 안전하게 만들고, 개인 생성자가 있습니다.