32

互いに参照する 2 つの列挙型を持つことによって引き起こされるクラスの読み込みの問題を回避する方法はありますか?

次のように定義された Foo と Bar の 2 つの列挙セットがあります。

public class EnumTest {

  public enum Foo {
    A(Bar.Alpha),
    B(Bar.Delta),
    C(Bar.Alpha);

    private Foo(Bar b) {
      this.b = b;
    }

    public final Bar b;
  }

  public enum Bar {
    Alpha(Foo.A),
    Beta(Foo.C),
    Delta(Foo.C);

    private Bar(Foo f) {
      this.f = f;
    }

    public final Foo f;
  }

  public static void main (String[] args) {
    for (Foo f: Foo.values()) {
      System.out.println(f + " bar " + f.b);
    }
    for (Bar b: Bar.values()) {
      System.out.println(b + " foo " + b.f);
    }
  }
}

上記のコードは、次の出力を生成します。

A bar Alpha
B bar Delta
C bar Alpha
Alpha foo null
Beta foo null
Delta foo null

なぜそれが起こるのか理解しています - JVM は Foo のクラスローディングを開始します。Foo.A のコンストラクターで Bar.Alpha を認識し、Bar のクラスローディングを開始します。Bar.Alpha のコンストラクターへの呼び出しで Foo.A 参照が確認されますが、(まだ Foo.A のコンストラクターにいるため) Foo.A はこの時点で null であるため、Bar.Alpha のコンストラクターには null が渡されます。2 つの for ループを逆にすると (または、Foo の前に Bar を参照すると)、Bar の値はすべて正しくなるように出力が変更されますが、Foo の値は正しくありません。

これを回避する方法はありますか?静的マップと 3 番目のクラスで静的マップを作成できることはわかっていますが、それはかなりハックな気がします。外部マップを参照する Foo.getBar() および Bar.getFoo() メソッドを作成することもできるので、インターフェイス (パブリック フィールドの代わりにインスペクターを使用している実際のクラス) も変更されませんが、それでも感じられます。私にはちょっと不潔です。

(実際のシステムでこれを行っている理由: Foo と Bar は、2 つのアプリが相互に送信するメッセージのタイプを表します。Foo.b フィールドと Bar.f フィールドは、特定のメッセージに対して予想される応答タイプを表します。サンプル コードでは、app_1 が Foo.A を受信すると、Bar.Alpha で応答する必要があり、その逆も同様です)。

前もって感謝します!

4

6 に答える 6

25

最良の方法の 1 つは、列挙型ポリモーフィズム手法を使用することです。

public class EnumTest {
    public enum Foo {
        A {

            @Override
            public Bar getBar() {
                return Bar.Alpha;
            }
        },
        B {

            @Override
            public Bar getBar() {
                return Bar.Delta;
            }
        },
        C {

            @Override
            public Bar getBar() {
                return Bar.Alpha;
            }
        },

        ;

        public abstract Bar getBar();
    }

    public enum Bar {
        Alpha {

            @Override
            public Foo getFoo() {
                return Foo.A;
            }
        },
        Beta {

            @Override
            public Foo getFoo() {
                return Foo.C;
            }
        },
        Delta {

            @Override
            public Foo getFoo() {
                return Foo.C;
            }
        },

        ;

        public abstract Foo getFoo();
    }

    public static void main(String[] args) {
        for (Foo f : Foo.values()) {
            System.out.println(f + " bar " + f.getBar());
        }
        for (Bar b : Bar.values()) {
            System.out.println(b + " foo " + b.getFoo());
        }
    }
}

上記のコードは、必要な出力を生成します。

A bar Alpha
B bar Delta
C bar Alpha
Alpha foo A
Beta foo C
Delta foo C

以下も参照してください。

于 2011-12-23T12:53:45.183 に答える
2

面白いデザイン。私はあなたの必要性を理解していますが、要件がわずかにシフトしたときに何をしますか?Foo.Epsilonに応答して、app_1はBar.GammaまたはBar.Whatsitのいずれかを送信する必要がありますか?

ハック(関係をマップに入れる)として検討して破棄したソリューションは、はるかに柔軟性があり、循環参照を回避しているようです。また、責任を分割して維持します。メッセージタイプ自体が、応答を知る責任を負わないようにする必要がありますか?

于 2009-10-02T11:32:40.837 に答える