1. 程式人生 > >【設計模式】之觀察者模式

【設計模式】之觀察者模式

觀察者模式

什麼是觀察者模式

  • 觀察者模式屬於行為模式的一種,定義了物件的通用交流方式。
  • 觀察者模式定義了一對多的關係,一個物件改變了狀態,則其它所有依賴它的物件都會收到通知。
  • 觀察者模式有時候在網路模型中也叫做釋出-訂閱模式。
  • 原來的物件叫做觀察者,觀察者們註冊的物件叫做主體。當主體狀態變更的時候,所有的觀察者都會收到通知。

觀察者模式的特點

  • 觀察者們註冊到主體物件中去。
  • 主體維護一個觀察者的列表,並且在其狀態發生變更的時候會廣播通知所有的觀察者。
  • 當明確不需要被通知的時候,觀察者可以登出。

觀察者模式的使用場景

  • 這種模式廣泛運用於使用者介面框架中。
  • 在很多MVC框架模型中經常使用。
  • 考慮一個excel文件中的表格的應用場景。一個表格中的圖示是根據其資料構建出來的,如果資料有任何變更,表格都會自動重繪。

觀察者模式類圖

觀察者模式類圖

觀察者模式示例

在這個示例中,描述了新聞釋出者。在典型的流程中,新聞閱讀者訂閱新聞。 一單一個新的新聞被出版商釋出了,所有的觀察者都會收到通知。 在這裡出版商的角色就是一個主體,訂閱者就是觀察者。 一個出版商可以有一個或者多個訂閱者。

上面的類圖中表現了2個觀察者 Subscriber1Subscriber2均註冊到了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());
        }
    }
}