觀察者模式(Observer Pattern)

2023-05-23 06:00:58

一、模式動機

觀察者模式用於描述物件之間的依賴關係,它引入了觀察者和觀察目標兩類不同的角色,由於提供了抽象層,它使得增加新的觀察者和觀察目標都很方便。觀察者模式廣泛應用於各種程式語言的事件處理模型中,Java語言也提供了對觀察者模式的全面支援。

  • 一個物件的狀態或行為的變化將導致其他物件的狀態或行為也發生改變,它們之間將產生聯動
  • 定義了物件之間一種一對多的依賴關係,讓一個物件的改變能夠影響其他物件
  • 發生改變的物件稱為觀察目標,被通知的物件稱為觀察者
  • 一個觀察目標可以對應多個觀察者

二、模式定義

  • 觀察者模式(Observer Pattern):定義物件間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新
  • 觀察者模式又叫做釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式
  • 觀察者模式是一種物件行為型模式

三、模式結構

抽象目標類

public abstract class Subject {
protected ArrayList observers<Observer> = new ArrayList();

public void attach(Observer observer) {
        observers.add(observer);    
}
public void detach(Observer observer) {
        observers.remove(observer);    
}
public abstract void notify( );
}

具體目標類

public class ConcreteSubject extends Subject {
    //實現通知方法
    public void notify() {
        for(Object obs:observers) {
            ((Observer)obs).update();
        }
    }	
}

抽象觀察者類

public interface Observer {
    public void update();
}

具體觀察者類

public class ConcreteObserver implements Observer {

    public void update() {
        ……
    }
}

四、案例實現

案例背景

股票

案例結構

程式碼實現

抽象目標類:股票

public interface Stocks {

    ArrayList<Observer> OBSERVERS = new ArrayList<>();
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyInvestor();

}

目標類:股票

public class Stock implements Stocks{

    private String stockName;
    private int price;

    public Stock(String stockName, int price) {
        this.stockName = stockName;
        this.price = price;
    }

    public Stock() {

    }

    public String getStockName() {
        return stockName;
    }

    public void setStockName(String stockName) {
        this.stockName = stockName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public void attach(Observer observer) {
        OBSERVERS.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        OBSERVERS.remove(observer);
    }

    @Override
    public void notifyInvestor() {
        if(price >= 25*1.05 || price <= 25*0.95){
            System.out.println("通知:股票價格變動幅度超過5%!");
            for (Observer observer: OBSERVERS) {
                observer.upDate();
            }
        } else {
            System.out.println("股票價格變化幅度沒有超過5%!!");
        }
    }
}

抽象觀察者類

public interface Observer {

    void upDate();

}

具體觀察者類A

public class ConcreteObserverA implements Observer {

    private String name;
    private Stock stock;

    public ConcreteObserverA(String name, Stock stock) {
        this.name = name;
        this.stock = stock;
    }

    public ConcreteObserverA(String name) {
        this.name = name;
    }

    @Override
    public void upDate() {
        System.out.println("通知"+name+":"+stock.getStockName()+"的股票價格變化幅度超過5%,股票價格為:"+stock.getPrice());
    }
}

具體觀察者類B

public class ConcreteObserverB implements Observer {

    private String name;

    public ConcreteObserverB(String name, Stock stock) {
        this.name = name;
        this.stock = stock;
    }

    public ConcreteObserverB(String name) {
        this.name = name;
    }

    private Stock stock;

    @Override
    public void upDate() {
        System.out.println("通知"+name+":"+stock.getStockName()+"的股票價格變化幅度超過5%,股票價格為:"+stock.getPrice());
    }
}

具體觀察者類C

public class ConcreteObserverC implements Observer {

    private String name;
    private Stock stock;

    public ConcreteObserverC(String name, Stock stock) {
        this.name = name;
        this.stock = stock;
    }

    public ConcreteObserverC(String name) {
        this.name = name;
    }

    @Override
    public void upDate() {
        System.out.println("通知"+name+":"+stock.getStockName()+"的股票價格變化幅度超過5%,股票價格為:"+stock.getPrice());
    }
}

測試類

public class Test {

    public static void main(String[] args) {
        System.out.println("設計模式,2020006924,於鑫");
        Stock stock1 = new Stock("股票1",25);
        System.out.println(stock1.getStockName()+"的股價為:"+stock1.getPrice());
        Observer obs1,obs2,obs3;
        obs1 = new ConcreteObserverA("smith",stock1);
        obs2 = new ConcreteObserverB("tom",stock1);
        obs3 = new ConcreteObserverC("李白",stock1);
        stock1.attach(obs1);
        stock1.attach(obs2);
        stock1.attach(obs3);
        stock1.setPrice(22);

        stock1.notifyInvestor();

    }

}

案例結果

五、模式分析

  • 有時候在具體觀察者類ConcreteObserver中需要使用到具體目標類ConcreteSubject中的狀態(屬性),會存在關聯或依賴關係
  • 如果在具體層之間具有關聯關係,系統的擴充套件性將受到一定的影響,增加新的具體目標類有時候需要修改原有觀察者的程式碼,在一定程度上違背了開閉原則,但是如果原有觀察者類無須關聯新增的具體目標,則系統擴充套件性不受影響

六、總結

模式優點

  • 可以實現表示層和資料邏輯層的分離
  • 在觀察目標和觀察者之間建立一個抽象的耦合
  • 支援廣播通訊,簡化了一對多系統設計的難度
  • 符合開閉原則,增加新的具體觀察者無須修改原有系統程式碼,在具體觀察者與觀察目標之間不存在關聯關係的情況下,增加新的觀察目標也很方便

模式缺點

  • 將所有的觀察者都通知到會花費很多時間
  • 如果存在迴圈依賴時可能導致系統崩潰
  • 沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而只是知道觀察目標發生了變化

使用情形

  • 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面,將這兩個方面封裝在獨立的物件中使它們可以各自獨立地改變和複用
  • 一個物件的改變將導致一個或多個其他物件發生改變,且並不知道具體有多少物件將發生改變,也不知道這些物件是誰
  • 需要在系統中建立一個觸發鏈