觀察者模式
又叫做發佈-訂閱(Publish/Subscribe)模式。觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
觀察者模式代碼及組成:
Subject類:可翻譯爲主題或抽象通知者,一般用一個抽象類或者一個接口實現。他把所有對觀察者對象的引用保存在一個聚集裏,每個主題都可以有任意多的觀察者。抽象主題提供一個接口,可以增加或刪除觀察者對象。
/**
* 抽象通知者
*/
public abstract class Subject {
private List<Observer> observers;
public Subject() {
observers = new ArrayList<>();
}
/**
* 添加觀察者
* @param observer
*/
public void attach(Observer observer){
observers.add(observer);
}
/**
* 移除觀察者
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知觀察者
*/
public void notifyObserver(){
observers.forEach(Observer::update);
}
}
Observer類:抽象觀察者,爲所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。這個接口叫做更新接口。抽象觀察者一般用一個抽象類或者一個接口實現。更新接口通常包含一個update()方法,這個方法叫做更新方法。
/**
* 抽象觀察者
*/
public abstract class Observer {
/**
* 觀察者被通知時更新狀態,和主體狀態保持一致
*/
public abstract void update();
}
ConcreteSubject類:具體主題或者具體通知者,將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知,具體主題角色通常用一個具體子類實現。
/**
* 具體通知者
*/
public class ConcreteSubject extends Subject {
/**
* 主題狀態
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
}
}
ConcreteObserver類:具體觀察者,實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。具體觀察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現。
/**
* 具體觀察者
*/
public class ConcreteObserver extends Observer {
/**
* 觀察者名稱
*/
private String name;
/**
* 觀察者狀態
*/
private String observerState;
/**
* 主體
*/
private ConcreteSubject subject;
public ConcreteObserver(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
@Override
public void update() {
this.observerState = subject.getSubjectState();
System.out.println("主體狀態改變,通知觀察者" + name + "。觀察者新狀態爲 " + observerState);
}
}
測試代碼:
public class ObserverTest {
public static void main(String[] args) {
// 創建主體
ConcreteSubject subject = new ConcreteSubject();
// 添加需要被通知到的觀察者
subject.attach(new ConcreteObserver("observer1", subject));
subject.attach(new ConcreteObserver("observer2", subject));
// 改變主題狀態,通知觀察者
subject.setSubjectState("XYZ");
subject.notifyObserver();
}
}
輸出:
主體狀態改變,通知觀察者observer1。觀察者新狀態爲 XYZ
主體狀態改變,通知觀察者observer2。觀察者新狀態爲 XYZ
觀察者模式特點
使用觀察者模式的動機?
將一個系統分割成一系列相互協作的類有一個很不好的副作用,那就是需要維護相關對象間的一致性。我們不希望爲了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。
觀察者模式的關鍵對象是主題Subject和觀察者Observer,一個Subject可以有任意數目的依賴它的Observer,一旦Subject的狀態發生了改變,所有的Observer都可以得到通知。Subject發出通知時並不需要知道誰是它的觀察者,也就是說,觀察者是誰,它根本不需要知道。而任何一個具體觀察者不知道也不需要知道其他觀察者的存在。
什麼時候使用觀察者模式?
當一個對象的改變需要同時改變其它對象的時候,而且它不知道具體有多少對象有待改變時,應該考慮使用觀察者模式。
當一個抽象模型有兩個方面,其中一方面依賴另一方面,這時使用觀察者模式可以將這兩者封裝在獨立對象中使他們各自獨立地改變和複用。
總的來講,觀察者模式所作的工作就是在解除耦合。讓耦合的雙方都依賴於抽象而不是具體。從而使得各自的變化都不會影響另一邊的變化。這就是依賴倒轉原則的最佳體現。
觀察者使用抽象類VS接口
現實編程中,具體的觀察者完全有可能是不相干的類,但他們都需要根據通知者的通知做出相應操作,所以讓它們都實現一個接口就可以了。
觀察者模式的不足&改進
不足:
儘管已經用了依賴倒轉原則,但是‘抽象通知者’還是依賴‘抽象觀察者’,也就是說,萬一沒有了抽線觀察者這樣的接口,通知的功能就完不成了。另外就是每個具體觀察者,他不一定是更新方法要調用,有可能要調用其它方法,這就是不足的地方
如果通知者和觀察者之間根本就互相不知道,由客戶端來決定通知誰那就好了。
改進:事件委託實現
可以通過事件委託來實現觀察者模式。
EventHandler:事件處理類,相當於主題,用來通知事件方法。
public class EventHandler {
/**
* 觀察者集合
*/
private List<Event> eventList;
public EventHandler() {
eventList = new ArrayList<>();
}
/**
* 添加觀察者
* @param e
*/
public void attach(Event e){
eventList.add(e);
}
/**
* 移除觀察者
* @param e
*/
public void detach(Event e){
eventList.remove(e);
}
/**
* 通知觀察者
*/
public void notifyEvent(){
eventList.forEach(Event::invoke);
}
}
Event:事件類,相當於觀察者,用來執行具體方法
public class Event {
/**
* 事件類(觀察者)
*/
private Object object;
/**
* 方法名稱(通知的方法)
*/
private String methodName;
/**
* 方法參數
*/
private Object[] params;
/**
* 參數類型
*/
private Class[] paramTypes;
public Event(Object object, String methodName, Object... params) {
this.object = object;
this.methodName = methodName;
this.params = params;
this.setParamTypes(params);
}
/**
* 設置參數類型
* @param params
*/
private void setParamTypes(Object[] params){
this.paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++){
paramTypes[i] = params[i].getClass();
}
}
/**
* 執行方法
*/
public void invoke(){
Method method = null;
try {
// 1. 獲取方法
method = object.getClass().getMethod(this.methodName, this.paramTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
if (method == null){
return;
}
try {
// 2. 執行方法
method.invoke(this.object, this.params);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
Subject類:主題類(通知者)
/**
* 主體(通知者)
*/
public interface Subject {
/**
* 通知方法
*/
void notifyObserver();
}
ConcreteSubject:通知者的具體實現
public class ConcreteSubject implements Subject {
/**
* 主體狀態
*/
private String subjectState;
/**
* 事件處理
*/
private EventHandler handler;
public ConcreteSubject() {
handler = new EventHandler();
}
public EventHandler getHandler() {
return handler;
}
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
}
@Override
public void notifyObserver() {
handler.notifyEvent();
}
}
ObserverA:觀察者A
public class ObserverA {
private String name;
private ConcreteSubject subject;
public ObserverA(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
public void observerAMethod(){
System.out.println("觀察者A name = " + name + " 收到主體狀態更新通知,state = " + subject.getSubjectState());
}
}
ObserverB:觀察者B
public class ObserverB {
private String name;
private ConcreteSubject subject;
public ObserverB(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
public void observerBMethod(LocalDateTime dateTime){
System.out.println("觀察者B name = " + name + " 收到主體狀態更新通知,state = " + subject.getSubjectState() + "日期:" + dateTime.toString());
}
}
觀察者模式——事件委託測試類
public class ObserverDelegateTest {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ObserverA observerA = new ObserverA("觀察者A", subject);
ObserverB observerB = new ObserverB("觀察者B", subject);
EventHandler handler = subject.getHandler();
handler.attach(new Event(observerA, "observerAMethod"));
handler.attach(new Event(observerB, "observerBMethod", LocalDateTime.now()));
subject.setSubjectState("subject更新狀態了");
subject.notifyObserver();
}
}
輸出:
觀察者A name = 觀察者A 收到主體狀態更新通知,state = subject更新狀態了
觀察者B name = 觀察者B 收到主體狀態更新通知,state = subject更新狀態了日期:2019-06-03T22:33:06.748
事件委託的方式優點
1. 主題(通知者)完全不知道觀察者的存在,實現了完全解耦。
2. 一次通知可以執行不同類型的方法。
3. 可擴展性強。