觀察者模式是一種比較經典的模式。被觀察者可以對應多個觀察者,是一種一對多的模式。被觀察者一旦有變動,就會通知到所有注冊的觀察者,被觀察者可以增加也可以移除,易于擴展。觀察者模式又被稱之為發布訂閱模式,監聽器模式等等,下面通過一個買票的例子來介紹一下這種模式
買票
買票,這個動作隱含了兩個主體,一個是人,一個是票,同時這個動作還包含了一個隱藏的動作,通知。好了,這個通知就是我們用觀察者模式的地方了。
大麥網有這么個功能,缺貨登記,就是有票了就告訴登記的人。那我們改造一下,變成票有變化,就通知登記人。
上面的例子可以很明顯分辨出來,票
就是被觀察的對象,人
是觀察者。
被觀察者
下一步,來梳理一下通知這部分需要哪些功能。
首先,肯定是我訂閱這個票的動態,暫時取名為addObserver
,其次有訂閱動態就有取消訂閱removeObserver
,當然最重要的還是通知notifyObservers
。
下面我們就定義一個抽象類Observable
---實現上面所提到的三個基本功能
package me.learn.pattern.observer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public abstract class Observable {
private List<Observer> observers = new LinkedList<>();
protected void addObserver(Observer observer) {
this.observers.add(observer);
}
protected void addObservers(Observer... observers) {
this.observers.addAll(Arrays.asList(observers));
}
protected void removeObserver(Observer observer) {
this.observers.remove(observer);
}
protected void removeAllObservers() {
this.observers.clear();
}
protected void notifyObservers(Object data) {
for (Observer observer : observers) {
observer.update(this, data);
}
}
}
觀察者
買票的另一主體是人
。那么一個人在這個過程中他想要的其實很簡單,就是通知到我,就像大麥的缺貨登記,我登記了,你要通知我,也就是調用我的方法,然后我可以做我收到這個通知之后的事情。那就很明確了,觀察者需要的就是一個類似更新update
的方法。我們定義一下這個觀察者的接口。
package me.learn.pattern.observer;
public interface Observer {
void update(Observable observable, Object data);
}
接下來只要定義票
繼承被觀察者基類,人
實現觀察者方法就可以了。
再說買票
其實,上面的過程已經說完了觀察者模式,但是讓我們再想想缺貨登記
。票和人,其實都既是觀察者也是被觀察者,票需要被觀察余量,也需要觀察人的購票;人既需要觀察票的動態,也是被觀察是否購票的對象。所以我們來寫一個既是觀察者也是被觀察者的例子吧。
首先定義票,繼承被觀察者,并實現觀察者。
package me.learn.pattern.observer;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public class Ticket extends Observable implements Observer {
private AtomicInteger left;
public Ticket() {
this.left = new AtomicInteger(0);
}
public Ticket(int left) {
this.left = new AtomicInteger(left);
}
public synchronized void setLeft(int left) {
this.left.set(left);
}
public synchronized void sold(int number) {
if (this.left.get() >= number) {
this.left.compareAndSet(this.left.get(), this.left.intValue() - number);
super.notifyObservers(this.left.get());
} else {
System.out.println("sold out");
}
}
public synchronized void soldAllTickets() {
this.left.set(0);
super.notifyObservers(this.left.get());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Ticket ticket = (Ticket) o;
return Objects.equals(left, ticket.left);
}
@Override
public int hashCode() {
return Objects.hash(left);
}
@Override
public String toString() {
return "Ticket";
}
@Override
public void update(Observable observable, Object data) {
System.out.println(observable + " buys "+data+" ticket");
sold((Integer) data);
}
}
其次,購票人。
package me.learn.pattern.observer;
public class PersonWhoWantTickets extends Observable implements Observer {
private String name;
public PersonWhoWantTickets(String name) {
this.name = name;
}
public void buyOneTicket() {
System.out.println(name + " buys one ticket");
super.notifyObservers(1);
}
@Override
public void update(Observable observable, Object data) {
System.out.println(this + " received notice# tickets left["+data+"]");
if ((int)data == 0) {
System.out.println("oh, i can not buy more");
} else {
buyOneTicket();
}
}
@Override
public String toString() {
return "PersonWhoWantTickets{" +
"name='" + name + '\'' +
'}';
}
public static void main(String[] argv) {
Ticket ticket = new Ticket(13);
PersonWhoWantTickets person = new PersonWhoWantTickets("person");
person.addObserver(ticket);
ticket.addObservers(person);
ticket.sold(5);
person.buyOneTicket();
}
}