本文實(shí)例代碼:https://github.com/JamesZBL/java_design_patterns
觀察者(Observer)模式實(shí)現(xiàn)了一種一對多的通信方式汉柒,它的核心是由多個(gè)對象監(jiān)聽中心對象,中心對象發(fā)生某種變化時(shí)將這個(gè)變化通知給關(guān)注它的周邊對象责鳍,所以碾褂,它又被稱為 “訂閱-發(fā)送” 模式。
在現(xiàn)實(shí)生活中也有許多和觀察者模式的運(yùn)作方式相似的例子历葛,比如當(dāng)我們訂閱某個(gè)公眾號(hào)后正塌,一旦該公眾號(hào)的主人發(fā)不了新的文章或消息,所有訂閱這個(gè)公眾號(hào)的用戶都會(huì)受到這個(gè)消息恤溶。這些用戶就對應(yīng)著觀察者模式里的訂閱者乓诽,公眾號(hào)對應(yīng)著發(fā)布者,一旦訂閱者對關(guān)注點(diǎn)失去了興趣咒程,就會(huì)取消對發(fā)布者的訂閱鸠天,也就不會(huì)再受到發(fā)布者的任何消息了。所以孵坚,實(shí)現(xiàn)觀察者模式的主要方法就是訂閱和發(fā)布。
實(shí)例
我們的祖國地大物博窥淆,南方人和北方人的生活習(xí)慣在某些方面的差異是很大的卖宠,這一點(diǎn)飲食習(xí)慣上可以非常容易看出來。所以我們現(xiàn)在用一天中不同時(shí)段忧饭,南方人和北方人的飲食活動(dòng)舉個(gè)例子扛伍。
現(xiàn)在有一個(gè)智能鬧鐘,南方人和北方人都在這個(gè)鬧鐘上定時(shí)词裤,每到特定時(shí)刻刺洒,這個(gè)鬧鐘就會(huì)分別提醒它的用戶。所以吼砂,用戶要統(tǒng)一實(shí)現(xiàn)一個(gè)接口逆航,以便鬧鐘能夠調(diào)用用戶中的方法來通知用戶,這個(gè)接口同時(shí)代表了訂閱者的關(guān)注點(diǎn)渔肩,即時(shí)間的變化因俐。首先定義一個(gè)枚舉類來表示不同的時(shí)間點(diǎn):
TimePoint.java
public enum TimePoint {
MORNING("早晨"), NOON("中午"), AFTERNOON("下午"), EVENING("晚上");
private String name;
TimePoint(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
所有鬧鐘的用戶都要實(shí)現(xiàn)的接口 TimeObserver
,通過接口實(shí)現(xiàn)了發(fā)布者向訂閱者傳遞消息:
TimeObserver.java
public interface TimeObserver {
void update(TimePoint time);
}
南方的朋友和北方的朋友分別實(shí)現(xiàn) TimeObserver
接口:
Southern.java
public class Southern implements TimeObserver {
private static final Logger LOGGER = LoggerFactory.getLogger(Southern.class);
@Override
public void update(TimePoint time) {
LOGGER.info("南方的朋友吃飯了");
switch (time) {
case MORNING:
LOGGER.info("熱干面");
break;
case NOON:
LOGGER.info("米飯");
break;
case AFTERNOON:
LOGGER.info("茶");
break;
case EVENING:
LOGGER.info("魚");
break;
default:
break;
}
}
}
Northern.java
public class Northern implements TimeObserver {
private static final Logger LOGGER = LoggerFactory.getLogger(Northern.class);
@Override
public void update(TimePoint time) {
LOGGER.info("北方人吃飯了");
switch (time) {
case MORNING:
LOGGER.info("煎餅果子");
break;
case NOON:
LOGGER.info("炸醬面");
break;
case AFTERNOON:
LOGGER.info("牛奶");
break;
case EVENING:
LOGGER.info("包子");
break;
default:
break;
}
}
}
下面開始定義時(shí)間點(diǎn)消息的發(fā)布者 Time
周偎,也就是上文中的鬧鐘:
Time.java
public class Time {
private static final Logger LOGGER = LoggerFactory.getLogger(Time.class);
private TimePoint point;
private List<TimeObserver> observers;
public Time() {
this.point = TimePoint.MORNING;
observers = new ArrayList<>();
}
public void addObserver(TimeObserver observer) {
observers.add(observer);
}
public void removeObserver(TimeObserver observer) {
observers.remove(observer);
}
public void passing() {
TimePoint[] points = TimePoint.values();
point = points[(point.ordinal() + 1) % points.length];
LOGGER.info("時(shí)間來到了{(lán)}", point);
notifyObservers();
}
public void notifyObservers() {
for (TimeObserver observer : observers) {
observer.update(point);
}
}
}
Time
對象持有一系列 TimeObserver
對象的引用抹剩,每當(dāng)時(shí)間點(diǎn)發(fā)生變化, Time
就會(huì)通過調(diào)用 TimeObserver
的 update()
方法蓉坎,下面模擬一下這個(gè)鬧鐘的使用過程:
App.java
public class Application {
public static void main(String[] args) {
Time time = new Time();
time.addObserver(new Northern());
time.addObserver(new Southern());
time.passing();
time.passing();
time.passing();
time.passing();
time.passing();
time.passing();
time.passing();
time.passing();
time.passing();
}
}
總結(jié)
這是整個(gè)包的類圖
觀察者模式的具體應(yīng)用形式可能多種多樣澳眷,但都包括以下幾點(diǎn)共性:
發(fā)布者(被觀察方)和訂閱者(觀察方)通常是一對多的關(guān)系,即發(fā)布者可以同時(shí)被多個(gè)訂閱者訂閱蛉艾,所以比較常用的方式是用發(fā)布者用集合的方式維護(hù)一系列訂閱者
可以通過控制發(fā)布者的實(shí)現(xiàn)細(xì)節(jié)來控制最終是否向某個(gè)訂閱者發(fā)布消息
訂閱者的關(guān)注點(diǎn)發(fā)生變化時(shí)钳踊,發(fā)布者通過接口回調(diào)來通知訂閱者
個(gè)人博客同步更新衷敌,獲取更多技術(shù)分享請關(guān)注:鄭保樂的博客