2011-10-20 47 views
5

Tôi đang cố gắng tạo một hệ thống để trả lời các sự kiện xảy ra trong ứng dụng của tôi, tương tự như mẫu Observer. Trong hệ thống của tôi, EventProducer s sự kiện kích hoạt và EventConsumer s ứng phó với những sự kiện, và cả hai đều được kết nối thông qua một trung tâm Trung ương:Cần trợ giúp bằng cách sử dụng Java Generics

Đối với thời điểm này, tôi sẽ bỏ qua EventProducer và tập trung vào EventHubEventConsumer:

interface EventConsumer<E extends Event> { 
    void respondToEvent(E event); 
} 

class EventHub { 
    private HashMap<Class</*event type*/>, HashSet<EventConsumer</*event type*/>>> subscriptions; 
    public <E extends Event> void fireEvent(E event) { 
     /* For every consumer in the set corresponding to the event type { 
      consumer.respondToEvent(event); 
     } */ 
    } 
    public <E extends Event> void subscribeToEvent(EventConsumer<E> consumer) { 
     /* Insert consumer into the set corresponding to E */ 
    } 
} 

vấn đề nằm ở việc kê khai của HashMap: tôi muốn để có thể làm điều gì đó như

HashMap<Class<E extends Event>, HashSet<EventConsumer<E>>> 
// or 
<E extends Event> HashMap<Class<E>, HashSet<EventConsumer<E>>> 

Vì vậy mà các EventConsumer được tham số hóa bởi cùng loại các Class là, nhưng gần nhất tôi có thể nhận được là

HashMap<Class<? extends Event>, HashSet<EventConsumer<? extends Event>>> 

Nhưng sau đó điều này sẽ cho phép những thứ như một HashSet<EventConsumer<MouseClickEvent>> được gán cho Class<KeyPressEvent>, giả sử cả KeyPressEventMouseClickEvent lớp con Event.

Một vấn đề thứ hai là trong subscribeToEvent: Tôi cần phải có khả năng lưu trữ cho người tiêu dùng trong các thiết lập chính xác tương ứng với sự kiện của nó, giống như trong

subscriptions.get(E.class).put(consumer) 

nhưng tôi không thể có được lớp của E tại thời gian chạy .

Tôi làm cách nào để giải quyết các vấn đề này? Tôi đang đi về điều này một cách sai lầm?

Trả lời

1

Bạn có thể xóa các generics khỏi lớp EventConsumer. Nhưng bạn phải bỏ đối tượng Sự kiện trong mỗi lần triển khai EventConsumer.

interface EventConsumer { 
    void respondToEvent(Event event); 
} 

class ClickEventConsumer implements EventConsumer { 
    public void respondToEvent(Event event){ 
    ClickEvent ce = (ClickEvent)event; 
    //... 
    } 
} 

class EventHub { 
    private HashMap<Class<? extends Event>, HashSet<EventConsumer>> subscriptions; 

    public void fireEvent(Event event) { 
    HashSet<EventConsumer> consumers = subscriptions.get(event.getClass()); 
    if (consumers != null){ 
     for (EventConsumer ec : consumers){ 
     ec.respondToEvent(event); 
     } 
    } 
    } 

    public void subscribeToEvent(Class<? extends Event> clazz, EventConsumer consumer) { 
    HashSet<EventConsumer> consumers = subscriptions.get(clazz); 
    if (consumers == null){ 
     consumers = new HashSet<EventConsumer>(); 
     subscriptions.put(clazz, consumers); 
    } 
    consumers.add(consumer); 
    } 
} 
+0

Thực ra, tôi thích cách tiếp cận này. Ban đầu, tôi đã muốn có thể làm một cái gì đó như 'lớp SomeConsumer thực hiện EventConsumer , EventConsumer ', nhưng dường như bạn không thể làm điều đó (mà tôi đoán có ý nghĩa).Có cách nào tôi có thể nhận được một số dạng đa hình 'Event' trên một người tiêu dùng, để tránh một sự chuyển đổi trong' respondToEvent'? Giống như 'void responseToEvent (ClickEvent); void respondToEvent (KeyPressEvent); '? –

+0

@AustinHyde Tôi không biết. Tôi đã chơi đùa với điều đó, nhưng tôi không thể tìm ra cách tốt để làm điều đó mà không tạo ra tất cả sự hỗn loạn của Generics trong 'EventHub'. Bạn cũng nên xem xét khả năng đọc của mã. Ngay cả khi nó hoạt động kém hiệu quả, nếu nó dễ dàng duy trì theo thời gian, tôi nghĩ nó đáng giá (trừ khi bạn đang viết một trò chơi trên máy tính hoặc một công cụ tìm kiếm hoặc thứ gì đó tính bằng mili giây). Đối với tôi, tất cả những generics đó làm cho mã khó đọc và dễ hiểu hơn. – Michael

1

Đối với Bản đồ, tôi muốn rời khỏi nó như sau:

HashMap<Class<? extends Event>, Set<EventConsumer<? extends Event>>> subscriptions; 

Và sau đó sử dụng phương pháp accessor tham số như:

<E extends Event> void addSubscription(Class<E> eventClass, EventConsumer<? super E> eventConsumer) 

<E extends Event> Set<EventConsumer<? super E>> getSubscriptions(Class<E> eventClass) 

Như bạn đã chỉ ra bạn không thể có được lớp sự kiện vào thời gian chạy, do đó, bạn sẽ cần phải có nó được cung cấp bởi người dùng API của bạn như ví dụ với chữ ký phương thức của addSubscription được cung cấp ở trên.

0

Tại sao bạn không tham số lớp EventHub của mình? bất kỳ challanges?

interface EventConsumer<E extends Event> { 
void respondToEvent(E event); 
} 

class EventHub<E extends Event> { 
private HashMap<Class<E>, HashSet<EventConsumer<E>>> subscriptions; 

public void fireEvent(E event) { 
/* 
* For every consumer in the set corresponding to the event type { 
* consumer.respondToEvent(event); } 
*/ 
    } 

    public void subscribeToEvent(EventConsumer<E> consumer) { 
    /* Insert consumer into the set corresponding to E */ 
} 
} 

Và sau đó sử dụng E trong tất cả các chữ ký chức năng của bạn.

EDIT 1: Được rồi, kể từ khi tôi hiểu câu hỏi của bạn rõ ràng hơn, ở đây chúng tôi đi:

EventConsumer Lớp của bạn có thể giữ một eventType (s) mà nó hỗ trợ/tay cầm. EvenType là một Enum. Trong Bản đồ của bạn, bạn lưu trữ Người tiêu dùng chống lại một Loại sự kiện.

+0

Đây không phải là tùy chọn vì 'EventHub' cần xử lý nhiều loại' Sự kiện'. Nếu tôi làm 'EventHub mới ', thì 'EventHub' sẽ chỉ hoạt động với' KeyPressEvent ', không phải là 'MouseClickEvent' –

+0

Lớp EventConsumer của bạn có thể giữ (các) EventType mà nó hỗ trợ/xử lý. EvenType là một Enum. Trong Bản đồ của bạn, bạn lưu trữ Người tiêu dùng chống lại một Loại sự kiện. –

2

Điều bạn có thể làm là bọc Bản đồ với lớp tham số riêng của nó. đưa ra tham số cho lớp - bạn có thể sử dụng nó trong bản đồ.một cái gì đó như thế:

public class EventsMap<E extends Event> { 
    HashMap<Class<E>, HashSet<E>> map; 
} 

Đối với đăng ký - Tôi sẽ sử dụng câu trả lời ty1824 của ..

+1

Có lý do nào tôi muốn bọc nó thay vì phân lớp không: 'lớp Sự kiệnMap mở rộng HashMap , HashSet >>' –

+1

@Austin Có: 1. Bạn sẽ chỉ thực hiện những phương pháp công khai được sử dụng bởi việc thực hiện của bạn cung cấp đóng gói tốt hơn 2. Điều này sẽ cho phép bạn kiểm tra đơn vị thực hiện mà không cần kiểm tra đơn vị HashMap gốc 3. Bạn sẽ dính vào phần thành phần Ưu tiên hơn Thừa kế http://en.wikipedia.org/wiki/Composition_over_inheritance – Gandalf

+0

@Gandalf Well * đó là * một khái niệm thú vị mà tôi chưa từng nghĩ tới. Tôi sẽ cần phải nhìn vào đó nhiều hơn, nhưng tôi nghĩ rằng nó thực sự có thể áp dụng cho nhiều ứng dụng này hơn những gì trong câu hỏi này ... Cảm ơn một tấn! –

0

Vâng, bắt đầu từ cuối cùng:

Trước tiên, bạn có thể thực sự có được generic loại kiểu tham số khi chạy . Chỉ là bạn chỉ có thể làm điều đó trong một trường hợp đặc biệt:

static class A<E extends EventObject> { 
} 

static class B extends A<MouseEvent> { 
} 

public static void main(String[] args) { 
    System.out.println(B.class.getGenericSuperclass()); 
} 

Note B là không chung chung, nhưng được thừa hưởng từ cha mẹ chung.

Nếu cha là một loại tham số, đối tượng Type hoàn trả phải phản ánh chính xác thực tế loại thông số được sử dụng trong mã nguồn.

Cho dù bạn có thể đặt nó vào bất kỳ việc sử dụng nào là một câu hỏi khác. Tôi không thử. Hầu hết các mã tôi thấy rõ ràng vượt qua một ví dụ Class như một workaround.

Thứ hai, Map ở đây không thể (tốt, theo như tôi biết) được khắc phục bằng generics. Bạn có thể triển khai một bản đồ an toàn cho khóa học, nhưng tôi nghĩ có một điều thú vị nữa cần xem xét: khi bạn kích hoạt sự kiện, bạn có thể gửi nó đến tất cả những người đã đăng ký lớp sự kiện cụ thể này tất cả những ai đã đăng ký với bố mẹ . Vì vậy, chỉ event.getClass() sẽ chỉ là một chút ít hơn đủ.

Các vấn đề liên quan