【設計模式】之觀察者模式
阿新 • • 發佈:2018-12-18
觀察者模式
什麼是觀察者模式
- 觀察者模式屬於行為模式的一種,定義了物件的通用交流方式。
- 觀察者模式定義了一對多的關係,一個物件改變了狀態,則其它所有依賴它的物件都會收到通知。
- 觀察者模式有時候在網路模型中也叫做釋出-訂閱模式。
- 原來的物件叫做觀察者,觀察者們註冊的物件叫做主體。當主體狀態變更的時候,所有的觀察者都會收到通知。
觀察者模式的特點
- 觀察者們註冊到主體物件中去。
- 主體維護一個觀察者的列表,並且在其狀態發生變更的時候會廣播通知所有的觀察者。
- 當明確不需要被通知的時候,觀察者可以登出。
觀察者模式的使用場景
- 這種模式廣泛運用於使用者介面框架中。
- 在很多MVC框架模型中經常使用。
- 考慮一個excel文件中的表格的應用場景。一個表格中的圖示是根據其資料構建出來的,如果資料有任何變更,表格都會自動重繪。
觀察者模式類圖
觀察者模式示例
在這個示例中,描述了新聞釋出者。在典型的流程中,新聞閱讀者訂閱新聞。 一單一個新的新聞被出版商釋出了,所有的觀察者都會收到通知。 在這裡出版商的角色就是一個主體,訂閱者就是觀察者。 一個出版商可以有一個或者多個訂閱者。
上面的類圖中表現了2個觀察者 Subscriber1
和 Subscriber2
均註冊到了publisher。
一旦Publisher狀態發生變更,兩個訂閱者都會收到通知。
Observer.java
package org. byron4j.cookbook.designpattern.observer;
/**
* 觀察者介面類
*/
public interface Observer {
public void update(String editon);
}
Subscriber1.java
package org.byron4j.cookbook.designpattern.observer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
* 觀察者1
*/
@AllArgsConstructor
@Data
@Builder
public class Subscriber1 implements Observer {
@Override
public void update(String editon) {
System.out.println("Subscriber1收到新的版本通知。" + editon);
}
}
Subscriber2.java
package org.byron4j.cookbook.designpattern.observer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
* 觀察者2
*/
@AllArgsConstructor
@Data
@Builder
public class Subscriber2 implements Observer {
@Override
public void update(String editon) {
System.out.println("Subscriber2收到新的版本通知。" + editon);
}
}
Subject.java
package org.byron4j.cookbook.designpattern.observer;
/**
* 主體
*/
public interface Subject {
/**
* 註冊主體
* @param observer
*/
public void registerObserver(Observer observer);
/**
* 移除訂閱
* @param observer
*/
public void removeObserver(Observer observer);
/**
* 移除所有的觀察者
*/
public void notifyObservers();
/**
* 初始化主體相關資訊
*/
public void initObservers();
}
Publisher.java
package org.byron4j.cookbook.designpattern.observer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 出版商--釋出者
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Publisher implements Subject{
/**
* 已註冊的觀察者列表
*/
private List<Observer> _observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
System.out.println("註冊觀察者:" + observer);
_observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
System.out.println("登出觀察者:" + observer);
_observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : _observers) {
observer.update("廣播訊息通知給:" + observer);
}
}
@Override
public void initObservers() {
if( null == _observers ){
_observers = new ArrayList<>();
}
}
}
ObserverTest.java
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.observer.*;
import org.junit.Test;
public class ObserverTest {
@Test
public void test(){
// 主體
Subject publisher = Publisher.builder().build();
publisher.initObservers();
// 觀察者註冊
Observer subscriber1 = Subscriber1.builder().build();
publisher.registerObserver(subscriber1);
Observer subscriber2= Subscriber2.builder().build();
publisher.registerObserver(subscriber2);
// 通知當前已註冊的觀察者
publisher.notifyObservers();
// 移除觀察者
publisher.removeObserver(subscriber2);
// 通知當前已註冊的觀察者
publisher.notifyObservers();
}
}
測試用例輸出結果
註冊觀察者:Subscriber1()
註冊觀察者:Subscriber2()
Subscriber1收到新的版本通知。廣播訊息通知給:Subscriber1()
Subscriber2收到新的版本通知。廣播訊息通知給:Subscriber2()
登出觀察者:Subscriber2()
Subscriber1收到新的版本通知。廣播訊息通知給:Subscriber1()
觀察者模式的優點
- 提供觀察者和可觀察者之間的鬆散耦合。主體僅僅需要知道觀察者列表而不關心他們的具體實現。所有的觀察者都是經主體通過單個訊息廣播廣而告之的。
- 在任何時候都可以新增或者移除觀察者。當主體需要新增新的觀察者時主體不需要做任何改動。
觀察者模式的缺陷
- 如果有時候出現問題的話,需要在觀察者鏈進行debug是非常麻煩的。
- 主體持有所有觀察者的引用,如果不用的觀察者沒有及時從主體中登出,很可能會導致記憶體洩漏。這個問題通常稱之為失效的監聽器問題。
經驗法則
- 當不需要再監聽主體時,需要明確地登出觀察者。
- 推薦主體使用弱引用維持觀察者列表,以避免記憶體洩漏。
觀察者模式的另一個示例
abstract class Observer {
protected Subject subject;
public abstract void update();
}
class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public void add(Observer o) {
observers.add(o);
}
public int getState() {
return state;
}
public void setState(int value) {
this.state = value;
execute();
}
private void execute() {
for (Observer observer : observers) {
observer.update();
}
}
}
class HexObserver extends Observer {
public HexObserver(Subject subject) {
this.subject = subject;
this.subject.add(this);
}
public void update() {
System.out.print(" " + Integer.toHexString(subject.getState()));
}
}
class OctObserver extends Observer {
public OctObserver(Subject subject) {
this.subject = subject;
this.subject.add( this );
}
public void update() {
System.out.print(" " + Integer.toOctalString(subject.getState()));
}
}
class BinObserver extends Observer {
public BinObserver(Subject subject) {
this.subject = subject;
this.subject.add(this);
}
public void update() {
System.out.print(" " + Integer.toBinaryString(subject.getState()));
}
}
public class ObserverTest {
public static void main( String[] args ) {
Subject sub = new Subject();
// Client configures the number and type of Observers
new HexObserver(sub);
new OctObserver(sub);
new BinObserver(sub);
Scanner scan = new Scanner(System.in);
for (int i = 0; i < 5; i++) {
System.out.print("\nEnter a number: ");
sub.setState(scan.nextInt());
}
}
}