Hatena::ブログ(Diary)

on the center line.

2008-06-26

JavaでFriendクラスを実現するためのパターン(フレンドアクセスブリッジパターン?)

| 09:29

以前、JavaでFriendクラスのような事を実現する方法というエントリを書いたのですが、もう少しすっきりした書き方を見つけたので紹介&再掲載しておきます。

【やりたいこと】Fooクラスのfooメソッドを、別パッケージのBarクラスにのみ公開したい

まず、Fooクラス、Barクラスを作成。

Foo.java

public class Foo {
  private void foo() { System.out.println("foooooo"); }
}

Bar.java

package b;
public class Bar {
  private void bar() {
    new Foo().foo(); // ←コンパイルエラー
  }
}

上記のBarクラスは、当然コンパイルエラーとなる。そこでまず、Fooクラスに特定のFriendからのアクセスを許可するためのBridge的な内部クラス(=FriendFoo)を追加する。

Foo.java

public class Foo {
  public static class FriendFoo {
    public void foo(Foo f) {
      f.foo();
    }
  }
  private void foo() { System.out.println("foooooo"); }
}

そして、BarクラスではFriendFooクラス経由で foo() メソッドを呼び出すようにする。

Bar.java

package b;
public class Bar {
  private static class FriendFoo friend = new Foo.FriendFoo();

  private void bar() {
    Foo f = new Foo();
    friend.foo(f); // FriendFoo.foo→Foo.foo
  }
}

以上でBarクラスからの呼び出しは成功。ただしこれだけだと、Barクラス以外からのアクセスできてしまうので、それを禁止するためのコードを、Fooクラス側に追加。

Foo.java

public class Foo {
  public static class FriendFoo {
    protected FriendFoo() {
      if (!this.getClass().getName().startWith("b.Bar"))
        throw new RuntimeException();
    }
    public void foo(Foo f) {
      f.foo();
    }
  }
  private void foo() { System.out.println("foooooo"); }
}

以上で完成。ここでは、FriendFooクラスのコンストラクタで、インスタンス化したクラスのクラス名をチェックすることで、公開範囲を限定しています。つまり、b.Barの内部クラスとして作成されたサブクラスからでなければ、FriendFooのインスタンスは作成できない、という具合に。

補足ですが、さらにここの条件を startWith("b") || startWith("c.dd") に変更すれば、"b"パッケージ以下すべて、および"c.dd"パッケージ以下すべてに公開、などといった細かい制御もできます。これはC++のフレンドクラスにはないメリットかも。