2009-10-01 30 views
29

Có cách nào để giải quyết các vấn đề tải lớp do có hai enums tham chiếu lẫn nhau không?Java Enums: Hai loại enum, mỗi loại chứa tham chiếu với nhau?

Tôi có hai bộ enumerations, Foo và Bar, được xác định như sau:

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

Đoạn mã trên tạo ra như đầu ra:

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

Tôi hiểu tại sao nó xảy ra - JVM bắt đầu classloading Foo; nó thấy Bar.Alpha trong hàm tạo của Foo.A, vì vậy nó bắt đầu nạp lớp Bar. Nó nhìn thấy tham chiếu Foo.A trong lời gọi đến hàm tạo của Bar.Alpha, nhưng (vì chúng ta vẫn còn trong hàm tạo của Foo.A) Foo.A là null vào thời điểm này, nên hàm tạo của Bar.Alpha được truyền vào một giá trị rỗng. Nếu tôi đảo ngược hai vòng lặp (hoặc tham chiếu Bar trước khi Foo), đầu ra thay đổi để các giá trị của Bar là chính xác, nhưng giá trị của Foo thì không.

Có cách nào để giải quyết vấn đề này không? Tôi biết tôi có thể tạo ra một bản đồ tĩnh và một bản đồ tĩnh trong một lớp thứ ba, nhưng điều đó cảm thấy khá khó chịu với tôi. Tôi cũng có thể làm cho các phương thức Foo.getBar() và Bar.getFoo() tham chiếu đến bản đồ bên ngoài, vì vậy nó thậm chí sẽ không thay đổi giao diện của tôi (các lớp thực tế tôi đã sử dụng thanh tra thay vì các trường công khai), nhưng nó vẫn cảm thấy loại ô uế với tôi.

(Lý do tôi làm điều này trong hệ thống thực tế của mình: Foo và Bar đại diện cho các loại thông báo mà 2 ứng dụng gửi cho nhau; các trường Foo.b và Bar.f thể hiện kiểu phản hồi mong đợi cho một thông báo đã cho - vì vậy trong mã mẫu của tôi, khi app_1 nhận được Foo.A, nó cần phải trả lời bằng Bar.Alpha và ngược lại.)

Cảm ơn bạn trước!

Trả lời

21

Một trong những cách tốt nhất sẽ được sử dụng enum đa hình kỹ thuật:

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

Đoạn mã trên sẽ cho kết quả bạn muốn:

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

Xem thêm:

+1

Dường như với tôi như quá phức tạp. @ weiji của câu trả lời là sạch hơn nhiều, IMO. Tại sao cách tiếp cận này tốt hơn (bạn nói "tốt nhất")? –

+3

@NoamNelke Tôi đã bình chọn cách tiếp cận của weiji, điều này rất thú vị. Mặc dù cá nhân tôi nghĩ tốt hơn cách tôi khuyên dùng vì tham chiếu tuần hoàn nằm trong enum của nó, khác biệt lớn giữa các câu trả lời của chúng tôi là tôi cũng cho phép bạn thực hiện bất kỳ logic thời gian chạy nào trước khi quay trở lại, ví dụ: 'public bar getBar (boolean nullIfAlpha) {return nullIfAlpha? null: Bar.Alpha; } '. Dù sao, tôi đã chỉnh sửa câu trả lời của tôi cho "một trong những điều tốt nhất" thay vào đó, vì nó có thể dựa trên ý kiến. Cảm ơn vì đã trả lời! – falsarella

10

Vấn đề không quá nhiều "hai enums tham chiếu lẫn nhau", có nhiều "hai enums tham chiếu lẫn nhau trong các hàm tạo của chúng". Tham chiếu vòng tròn này là một phần khó khăn.

Cách sử dụng phương thức Foo.setResponse(Bar b)Bar.setResponse(Foo f)? Thay vì thiết lập Foo's Bar trong hàm tạo Foo (và tương tự như Foo của Bar trong hàm tạo Bar), bạn có khởi tạo bằng cách sử dụng một phương thức không? Ví dụ .:

Foo:

public enum Foo { 
    A, B, C; 

    private void setResponse(Bar b) { 
    this.b = b; 
    } 

    private Bar b; 

    public Bar getB() { 
    return b; 
    } 

    static { 
    A.setResponse(Bar.Alpha); 
    B.setResponse(Bar.Delta); 
    C.setResponse(Bar.Alpha); 
    } 
} 

Bar:

public enum Bar { 
    Alpha, Beta, Delta; 

    private void setResponse(Foo f) { 
    this.f = f; 
    } 

    private Foo f; 

    public Foo getF() { 
    return f; 
    } 

    static { 
    Alpha.setResponse(Foo.A); 
    Beta.setResponse(Foo.C); 
    Delta.setResponse(Foo.C); 
    } 
} 

Ngoài ra, bạn đề cập rằng Foo và Bar là hai loại thông điệp. Có thể kết hợp chúng thành một loại duy nhất không? Từ những gì tôi có thể thấy, hành vi của họ ở đây là như nhau. Điều này không sửa chữa logic tròn, nhưng nó có thể cung cấp cho bạn một số cái nhìn sâu sắc khác vào thiết kế của bạn ...

+0

Này, tôi đã [sửa câu trả lời của bạn] (http://stackoverflow.com/posts/1506635/revisions) để sửa chữa và cải thiện nó. Vui lòng chỉnh sửa lại hoặc khôi phục nếu bạn không đồng ý. – falsarella

3

Kể từ khi có vẻ như bạn đang đi để được cứng mã hóa anyways, tại sao không có một cái gì đó giống như

public static Bar responseBar(Foo f) { 
switch(f) { 
    case A: return Bar.Alpha; 
    // ... etc 
} 
} 

cho mỗi enum? Có vẻ như bạn có một số câu trả lời trùng lặp trong ví dụ của mình, vì vậy, bạn thậm chí có thể tận dụng các trường hợp gặp phải.

EDIT:

Tôi thích đề xuất của Tom về EnumMap; I nghĩ rằng hiệu suất có thể nhanh hơn trên EnumMap, nhưng kiểu xây dựng trang nhã được mô tả trong Java hiệu quả dường như không được chi trả bởi vấn đề cụ thể này - tuy nhiên, giải pháp chuyển đổi được cung cấp ở trên sẽ là một cách tốt để xây dựng hai tĩnh EnumMaps, sau đó phản hồi có thể là một cái gì đó như:

public static Bar response(Foo f) { return FooToBar.get(f); } 
public static Foo response(Bar b) { return BarToFoo.get(b); } 
+1

Hoặc một 'EnumMap' nếu bạn thích điều đó với' chuyển'. –

+0

(Cẩn thận làm thế nào bạn intialise rằng mặc dù - xem Java hiệu quả.) –

1

Thiết kế thú vị. Tôi thấy nhu cầu của bạn, nhưng bạn sẽ làm gì khi yêu cầu thay đổi một chút, để đáp ứng với Foo.Epsilon, app_1 phải gửi hoặc một Bar.Gamma hoặc Bar.Whatsit?

Giải pháp bạn xem xét và loại bỏ dưới dạng hackish (đưa quan hệ vào bản đồ) dường như mang đến cho bạn sự linh hoạt hơn nhiều và tránh tham chiếu vòng tròn của bạn. Nó cũng giữ trách nhiệm phân chia: các loại tin nhắn mình không nên chịu trách nhiệm cho biết phản ứng của họ, phải không?

0

Bạn có thể sử dụng EnumMap và điền vào trong một trong các enums.

private static EnumMap<Foo, LinkedList<Bar>> enumAMap; 

public static void main(String[] args) throws Exception { 
    enumAMap = new EnumMap<Foo, LinkedList<Bar>>(Foo.class); 
    System.out.println(Bar.values().length); // initialize enums, prevents NPE 
    for (Foo a : Foo.values()) { 
     for (Bar b : enumAMap.get(a)) { 
      System.out.println(a + " -> " + b); 
     } 
    } 
} 

public enum Foo { 
    Foo1(1), 
    Foo2(2); 

    private int num; 

    private Foo(int num) { 
     this.num = num; 
    } 

    public int getNum() { 
     return num; 
    } 
} 

public enum Bar { 
    Bar1(1, Foo.Foo1), 
    Bar2(2, Foo.Foo1), 
    Bar3(3, Foo.Foo2), 
    Bar4(4, Foo.Foo2); 

    private int num; 
    private Foo foo; 

    private Bar(int num, Foo foo) { 
     this.num = num; 
     this.foo = foo; 
     if (!enumAMap.containsKey(foo)) { 
      enumAMap.put(foo, new LinkedList<Bar>()); 
     } 
     enumAMap.get(foo).addLast(this); 
    } 

    public int getNum() { 
     return num; 
    } 

    public Foo getFoo() { 
     return foo; 
    } 
} 

Output:

4 
Foo1 -> Bar1 
Foo1 -> Bar2 
Foo2 -> Bar3 
Foo2 -> Bar4 
Các vấn đề liên quan