觀察者模式、發布訂閱和事件驅動
觀察者模式(有時又被稱為模型(Model)-視圖(View)模式、源-收聽者(Listener)模式或從屬者模式)是軟件設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實現事件處理系統。
觀察者模式(Observer)完美的將觀察者和被觀察的對象分離開,以明星和粉絲舉例子,明星就是被觀察著,粉絲就是觀察者。下面是一個demo
package Observer; public interface Observer { void update(Observable o); }
package Observer; import java.util.ArrayList; import java.util.List; public class Observable { private List<Observer> list = new ArrayList<>(); public void addObserver(Observer o) { list.add(o); } private String status; public List<Observer> getList() {return list; } public String getStatus() { return status; } public void movelUp (String status) { System.out.println(status); this.status = status; list.stream().forEach(o -> o.update(this)); } }
package Observer; public class Client {public static void main(String[] args) { Observable ob = new Observable(); ob.addObserver(new ObserverImpl("小花")); ob.addObserver(new ObserverImpl("小名")); ob.movelUp("天下無賊"); } }
以上便是標準的觀察者模式,在被觀察著中維護了一個觀察者的列表,需要由被觀察者去完成註冊觀察者的功能,不符合單一職責原則。所以我們需要添加一個manager類,來方便用戶自己去訂閱某個明星。改造代碼如下:
package Observer2; import java.util.ArrayList; import java.util.List; public class Star { private String name; private String lastMovel; // 標識被觀察者是否變化 private boolean changed = false; private List<Observer> list = new ArrayList<>(); public void addObserver(Observer o) { list.add(o); } public List<Observer> getList() { return list; } public void notifyObserver() { if (!changed) { return; } setChanged(false); list.stream().forEach(o -> o.update(this, null)); } public boolean isChanged() { return changed; } public void setChanged(boolean changed) { this.changed = changed; } public void setList(List<Observer> list) { this.list = list; } public Star(String name) { super(); this.name = name; Manager.getInstance().addStar(this); } public void movelUp(String movel) { System.out.println(name + "發布了" + movel); this.lastMovel = movel; setChanged(true); notifyObserver(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLastMovel() { return lastMovel; } public void setLastMovel(String lastMovel) { this.lastMovel = lastMovel; } }
package Observer2; public interface Observer { void update(Star o,Object args); }
package Observer2; public class Fans implements Observer { private String name; public Fans(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void subscribe(String starName) { Manager.getInstance().getStar(starName).addObserver(this); } @Override public void update(Star star,Object args) { System.out.println(name + "得到了" + star.getName() +"發布" + star.getLastMovel() + "的消息"); } }
package Observer2; import java.util.HashMap; import java.util.Map; public class Manager { private Map<String, Star> observableMap = new HashMap<>(); private Manager() { } public void addStar(Star star) { observableMap.put(star.getName(), star); } public Star getStar(String name) { return observableMap.get(name); } public static Manager getInstance() { return SingleManager.manager; } private static class SingleManager{ private static Manager manager = new Manager(); } }
package Observer2; public class Client { public static void main(String[] args) { Fans fans1 = new Fans("小明"); Fans fans2 = new Fans("小話"); Fans fans3 = new Fans("小張"); Fans fans4 = new Fans("小裏"); Star star1 = new Star("麗影"); Star star2 = new Star("冪冪"); fans1.subscribe("麗影"); fans3.subscribe("冪冪"); fans4.subscribe("冪冪"); star1.movelUp("乘風破浪"); star2.movelUp("孤島驚魂"); System.out.println("----------------------------"); fans2.subscribe("麗影"); star1.movelUp("女兒國"); } }
發布/訂閱模式(Pub/Sub)是一種消息模式,它有 兩個參與者 : 發布者和訂閱者 。發布者向 某個信道發布一條消息,訂閱者綁定這個信道,當有消息發布至信道時就會 接收到一個通知。最重要的一點是, 發布者和訂閱者是完全解耦的,彼此並不知曉對方的存在。從定義上可以看出,發布訂閱模式裏雙方是完全解耦的,而在觀察者模式裏,目標對象管理這觀察者,雙方是耦合的,這是最主要的區別,而在發布訂閱模式中多了一個中間層信道。我們常用的activeMQ、rabbitMQ、kafka等中間件就是為發布訂閱模式服務的。
還有一種和觀察者模式很像的就是事件驅動模型,相信各位都知道tomcat,在使用的過程中,或許經常會有人用到listener,即監聽器這個概念。那麽其實這個就是一個事件驅動模型的應用。比如我們的spring,我們在應用啟動的時候要初始化我們的IOC容器,那麽我們的做法就是加入一個listener,這樣伴隨著tomcat服務器的啟動,spring的IOC容器就會跟著啟動。那麽這個listener其實就是事件驅動模型中的監聽器,它用來監聽它所感興趣的事,比如我們springIOC容器啟動的監聽器,就是實現的ServletContextListener這個接口,說明它對servletContext感興趣,會監聽servletContext的啟動和銷毀。事件驅動模型與觀察者模式勉強的對應關系可以看成是,被觀察者相當於事件源,觀察者相當於監聽器,事件源會產生事件,監聽器監聽事件。所以這其中就攙和到四個類,事件源,事件,監聽器以及具體的監聽器。下面我們就將上面的例子改造為事件驅動模型。
package Observer3; import java.util.EventObject; public class MovelEvent extends EventObject { private static final long serialVersionUID = -1231609728871248531L; public MovelEvent(Star star) { super(star); } public Star getStar() { return (Star) super.getSource(); } }
package Observer3; import java.util.ArrayList; import java.util.List; public class Star { // 標識被觀察者是否變化 private boolean changed = false; private List<MovelListener> list = new ArrayList<>(); public void addMovelListener(MovelListener o) { list.add(o); } public List<MovelListener> getList() { return list; } public void setList(List<MovelListener> list) { this.list = list; } public void notifyObserver() { if (!changed) { return; } setChanged(false); MovelEvent movelEvent = new MovelEvent(this); list.stream().forEach(o -> o.update(movelEvent)); } public boolean isChanged() { return changed; } public void setChanged(boolean changed) { this.changed = changed; } public Star(String name) { super(); this.name = name; Manager.getInstance().addStar(this); } private String name; private String lastMovel; public void movelUp(String movel) { System.out.println(name + "發布了" + movel); this.lastMovel = movel; setChanged(true); notifyObserver(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLastMovel() { return lastMovel; } public void setLastMovel(String lastMovel) { this.lastMovel = lastMovel; } }
package Observer3; import java.util.EventListener; public interface MovelListener extends EventListener { void update (MovelEvent movelEvent); }
package Observer3; public class Fans implements MovelListener { private String name; public Fans(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void subscribe(String starName) { Manager.getInstance().getStar(starName).addMovelListener(this); } @Override public void update(MovelEvent movelEvent) { Star star = movelEvent.getStar(); System.out.println(name + "得到了" + star.getName() +"發布" + star.getLastMovel() + "的消息"); } }
我們徹底將剛才的觀察者模式改成了事件驅動,現在我們使用事件驅動的類再運行一下客戶端,其中客戶端代碼和WriterManager類的代碼是完全不需要改動的,直接運行客戶端即可。我們會發現得到的結果與觀察者模式一模一樣。
那麽觀察者模式和事件驅動的不同之處在哪裏呢?首先從實現方式上就能看出,事件驅動可以解決觀察者模式的問題,但反過來則不一定,另外二者所表達的業務場景也不一樣,比如上述例子,使用觀察者模式更貼近業務場景的描述,而使用事件驅動,從業務上講,則有點勉強。再者從功能上講,觀察者模式中觀察者的響應理論上講針對特定的被觀察者是唯一的(當然如果在update方法中ifelse則也能實現對多個被觀察著);事件驅動則更靈活,可以定義自己感興趣的事情,比如我們可以監聽movel,也可以在接口裏加一個singListener的接口,然後我們的fans同時實現這個接口,那麽star在發布singEvent的時候,只要實現了singListener的類的實例就可以收到消息,當然程序的復雜性就增加了。
最後總結一下,觀察者模式和發布訂閱模型的區別就在於消息是否發送給中間件,觀察者和被觀察著是否完全解耦;而觀察者模式和事件驅動的區別則在於事件驅動則更加靈活,但同時增加了程序的復雜性。
觀察者模式、發布訂閱和事件驅動