Java - A Self-defined EventObject and EventListener example

對於事件的概念我想第一次的接觸莫過於UI上的元件,例如當點擊按鈕後要在Label上顯示某些字樣。但在很多實務上我們也有自訂事件監聽者與發送事件的必要,如某個元件做了選擇,而在另一個元件上會接收到這個選擇事件,這種客製的行為就需要透過自訂事件。在這邊會舉個範例一步步看如何做到
#Device interface
  1. public interface IDevice {
  2. public String getName();
  3. public Status getStatus();
  4. public void init();
  5. public void open();
  6. public void runing();
  7. public void close();
  8. public void dispose();
  9. }

#抽象Device
  1. public abstract class Device implements IDevice{
  2. private String name;
  3. private Status status;
  4. public Device(String name) {
  5. this.name = name;
  6. init();
  7. }
  8. @Override
  9. public void init() {
  10. System.out.println(getName()+" is initializing");
  11. setStatus(Status.INITIALIZAE);
  12. }
  13. @Override
  14. public void open() {
  15. System.out.println(getName()+" is opened");
  16. setStatus(Status.OPEN);
  17. }
  18. @Override
  19. public void runing() {
  20. System.out.println(getName()+" is running");
  21. setStatus(Status.RUNNING);
  22. }
  23. @Override
  24. public void close() {
  25. System.out.println(getName()+" is closed");
  26. setStatus(Status.CLOSE);
  27. }
  28. @Override
  29. public void dispose() {
  30. }
  31. @Override
  32. public String toString() {
  33. return getName();
  34. }
  35. @Override
  36. public String getName() {
  37. return "'"+name+"'";
  38. }
  39. @Override
  40. public Status getStatus() {
  41. return status;
  42. }
  43. private void setStatus(Status status) {
  44. this.status = status;
  45. }
  46. }

#Status
  1. public enum Status {
  2. INITIALIZAE, OPEN, RUNNING, CLOSE;
  3. }

#事件觸發者 (Controller)
  1. public class Controller extends Device{
  2. public Controller(String name) {
  3. super(name);
  4. // TODO Auto-generated constructor stub
  5. }
  6. @Override
  7. public void open() {
  8. super.open();
  9. StatusEventManager.getInstance().dispatchStatusUpdate(new StatusEvent(this));
  10. }
  11. @Override
  12. public void close() {
  13. super.close();
  14. StatusEventManager.getInstance().dispatchStatusUpdate(new StatusEvent(this));
  15. }
  16. }

Controller繼承Device,在呼叫open及close時會利用StatusEventManager來發送一個StatusEvent的事件

#事件監聽者 (Receiver)
  1. public class Receiver extends Device implements StatusListener{
  2. public Receiver(String name) {
  3. super(name);
  4. // TODO Auto-generated constructor stub
  5. StatusEventManager.getInstance().addListener(this);
  6. }
  7. @Override
  8. public void updateStatus(StatusEvent event) {
  9. // TODO Auto-generated method stub
  10. Object source = event.getSource();
  11. if(source instanceof IDevice) {
  12. IDevice control = (IDevice) source;
  13. if(control.getStatus() == Status.OPEN) {
  14. runing();
  15. }else if(control.getStatus() == Status.CLOSE) {
  16. close();
  17. }
  18. System.out.println("======================================================");
  19. }
  20. }
  21. @Override
  22. public void dispose() {
  23. // TODO Auto-generated method stub
  24. StatusEventManager.getInstance().removeListener(this);
  25. }
  26. }

Receiver除了繼承Device外,還另外實作了StatusListener,在初始化時呼叫StatusEventManager註冊其為監聽者! 而在監聽到事件後會在updateStatus上做相對的行為

#事件分派者 (StatusEventManager)
  1. public class StatusEventManager {
  2. private List<StatusListener> listeners;
  3. private static StatusEventManager instance;
  4. private StatusEventManager() {
  5. }
  6. public static StatusEventManager getInstance() {
  7. if(instance == null) {
  8. instance = new StatusEventManager();
  9. }
  10. return instance;
  11. }
  12. public void addListener(StatusListener stateListener) {
  13. if (listeners == null) {
  14. listeners = new ArrayList<>();
  15. }
  16. System.out.println("Register "+stateListener+" listener to StatusEventManager");
  17. listeners.add(stateListener);
  18. }
  19. public void removeListener(StatusListener stateListener) {
  20. if (listeners == null)
  21. return;
  22. System.out.println("Remove "+stateListener+" listener from StatusEventManager");
  23. listeners.remove(stateListener);
  24. }
  25. public void dispatchStatusUpdate(StatusEvent e) {
  26. if(listeners != null) {
  27. for(StatusListener stateListener : listeners) {
  28. Object src = e.getSource();
  29. if(src instanceof IDevice) {
  30. IDevice dev = (IDevice) src;
  31. System.out.println("### "+dev+" dispatch \""+dev.getStatus()+"\" status to "+stateListener+" by StatusEventManager");
  32. stateListener.updateStatus(e);
  33. }
  34. }
  35. }
  36. }
  37. }

StatusEventManager主要是分派事件給監聽者,只要是註冊在它下面的listener都可以收到事件的物件

#監聽者 (StatusListener)
  1. public interface StatusListener extends EventListener{
  2. public void updateStatus(StatusEvent event);
  3. }

定義監聽者要實作的行為

#事件物件 (StatusEvent.java)
  1. public class StatusEvent extends EventObject{
  2. public StatusEvent(Object source) {
  3. super(source);
  4. // TODO Auto-generated constructor stub
  5. }
  6. }
包裝事件是由哪個source所觸發的

#主程式 (Main.java)
前面的各支程式介紹過後,接下來示範如何操作
  1. public class Main {
  2. public static void main(String[] args) {
  3. IDevice control = new Controller("Remote Controller");
  4. IDevice receiver1 = new Receiver("Television");
  5. IDevice receiver2 = new Receiver("Air conditioner");
  6. System.out.println("======================================================");
  7. showStatus(control, receiver1, receiver2);
  8. control.open();
  9. showStatus(control, receiver1, receiver2);
  10. control.close();
  11. showStatus(control, receiver1, receiver2);
  12. receiver1.dispose();
  13. receiver2.dispose();
  14. }
  15. public static void showStatus(IDevice ...device) {
  16. System.out.println("[Query device status]");
  17. for(IDevice d : device) {
  18. System.out.println(d.getName() + " status is "+d.getStatus());
  19. }
  20. System.out.println();
  21. }
  22. }

首先建構出一遙控器(Controller)、兩個接收器(Receiver),Receiver在建構當下會呼叫StatusEventManager註冊這個listener。當遙控器呼叫open後,會觸發StatusEventManager去進行事件分派的動作,也就是將手上所註冊的listener,輪詢一遍呼叫listener實作的updateStatus。透過updateStatus傳入的event object,從中取得目前遙控器的狀態,藉此改變Receiver的狀態成RUNNING。最後當Receiver不須使用後,記得呼叫dispose將listener從StatusEventManager上移除掉

執行結果如下:

留言