- 設(shè)計(jì)模式解析一 工廠模式的不同
- 設(shè)計(jì)模式解析二 結(jié)構(gòu)模式三劍客
- 設(shè)計(jì)模式解析三 行為模式三劍客
- 設(shè)計(jì)模式解析四 模板方法模式和外觀模式
- 設(shè)計(jì)模式解析五 觀察者模式和橋接模式
- 設(shè)計(jì)模式解析六 單例模式
一. 前言
第五篇要講的也是一個(gè)行為型模式,觀察者模式,話不多說,直接開始吧。
二. 觀察者模式
不知道大家有沒有向出版社訂閱過雜志的經(jīng)歷,我小時(shí)候定過,隱約記得拿本書叫《青年文摘》。
那么出版社有這么多雜志類型,有娛樂雜志、有經(jīng)濟(jì)雜志、有科技雜志,我們訂閱的時(shí)候選擇其中一種或幾種來訂閱,雜志出版的時(shí)候就會(huì)像所有訂閱了雜志的人郵寄雜志,事實(shí)上這種出版+訂閱的模式就是觀察者模式。
我們來實(shí)現(xiàn)代碼,首先我們定一個(gè)觀察者,也就是我們這些要訂書的訂閱者們的接口:
public interface Observer {
void post(String book);
}
再定義個(gè)雜志社:
public enum MagazineType {
// 娛樂雜志
ENTERTAINMENT,
// 科技雜志
SCIENCE
}
public interface PeriodicalOffice {
/**
* 訂閱雜志
*/
void subscribe(Observer observer,MagazineType type);
/**
* 取消訂閱
*/
void unSubscribe(Observer observer,MagazineType type);
/**
* 出版雜志,出版時(shí)會(huì)向所有訂閱者郵寄雜志
*/
void publish(MagazineType type, String book);
}
讓我們開一家星星雜志社好了:
public class StarPeriodicalOffice implements PeriodicalOffice {
// 存儲(chǔ)所有訂閱者
private static final Map<MagazineType, List<Observer>> observers = new ConcurrentHashMap<>();
@Override
public void subscribe(Observer observer, MagazineType type) {
List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
if (!list.contains(observer)) {
list.add(observer);
observers.put(type, list);
}
}
@Override
public void unSubscribe(Observer observer, MagazineType type) {
List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
list.remove(observer);
}
@Override
public void publish(MagazineType type, String book) {
List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
list.forEach(o -> o.post(book));
}
}
杰克和蘿絲來訂閱雜志了:
public class Jack implements Observer {
@Override
public void post(String book) {
System.out.println("杰克收到了雜志社郵寄來的書:" + book);
}
}
public class Rose implements Observer {
@Override
public void post(String book) {
System.out.println("蘿絲收到了雜志社郵寄來的書:" + book);
}
}
接下來是雜志訂閱和發(fā)布的過程:
public static void main(String[] args) {
PeriodicalOffice office = new StarPeriodicalOffice();
Observer jack = new Jack();
Observer rose = new Rose();
office.subscribe(jack, MagazineType.ENTERTAINMENT);
office.subscribe(jack, MagazineType.SCIENCE);
office.subscribe(rose, MagazineType.ENTERTAINMENT);
office.publish(MagazineType.SCIENCE, "科學(xué)故事");
office.publish(MagazineType.ENTERTAINMENT, "娛樂圈的故事");
}
執(zhí)行結(jié)果:
杰克收到了雜志社郵寄來的書:科學(xué)故事
杰克收到了雜志社郵寄來的書:娛樂圈的故事
蘿絲收到了雜志社郵寄來的書:娛樂圈的故事
這就是觀察者模式,觀察者模式在spring框架中也有運(yùn)用。
Spring的事件監(jiān)聽機(jī)制就是觀察者模式的實(shí)現(xiàn):
@Configuration
public class AppEvent implements ApplicationListener<ApplicationEvent> {
private static final LoggerAdapter LOGGER = LoggerFactory.getLogger(AppEvent.class);
@Autowired
private Environment environment;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
LOGGER.info("應(yīng)用已啟動(dòng), 當(dāng)前運(yùn)行的端口是: {} ", environment.getProperty("server.port"));
return;
}
if (event instanceof ApplicationFailedEvent) {
LOGGER.info("應(yīng)用啟動(dòng)失敗");
}
}
}
這里的監(jiān)聽是對(duì)所有應(yīng)用ApplicationEvent事件監(jiān)聽,當(dāng)然泛型也可以選擇針對(duì)某個(gè)之間進(jìn)行監(jiān)聽,比如只監(jiān)聽ApplicationReadyEvent。
public interface ApplicationEventMulticaster {
/**
* Add a listener to be notified of all events.
* @param listener the listener to add
*/
void addApplicationListener(ApplicationListener<?> listener);
/**
* Add a listener bean to be notified of all events.
* @param listenerBeanName the name of the listener bean to add
*/
void addApplicationListenerBean(String listenerBeanName);
/**
* Remove a listener from the notification list.
* @param listener the listener to remove
*/
void removeApplicationListener(ApplicationListener<?> listener);
/**
* Remove a listener bean from the notification list.
* @param listenerBeanName the name of the listener bean to add
*/
void removeApplicationListenerBean(String listenerBeanName);
/**
* Remove all listeners registered with this multicaster.
* <p>After a remove call, the multicaster will perform no action
* on event notification until new listeners are being registered.
*/
void removeAllListeners();
/**
* Multicast the given application event to appropriate listeners.
* <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
* if possible as it provides a better support for generics-based events.
* @param event the event to multicast
*/
void multicastEvent(ApplicationEvent event);
/**
* Multicast the given application event to appropriate listeners.
* <p>If the {@code eventType} is {@code null}, a default type is built
* based on the {@code event} instance.
* @param event the event to multicast
* @param eventType the type of event (can be null)
* @since 4.2
*/
void multicastEvent(ApplicationEvent event, ResolvableType eventType);
}
ApplicationEventMulticaster類就是所有的觀察者管理類,所有監(jiān)聽器都會(huì)通過這個(gè)接口來進(jìn)行注冊(cè),遇到應(yīng)用的事件發(fā)生時(shí)也通過該接口來進(jìn)行通知。至于是怎么注冊(cè)進(jìn)來的,就是在spring 容器加載bean時(shí)候判斷我們的bean是否是ApplicationListener的實(shí)現(xiàn)類,如果是就幫我們注冊(cè)進(jìn)來,有興趣的可以去看看源碼。
實(shí)際上Spring的源碼可以說是集設(shè)計(jì)模式大成者,我了解的也是百不足一,其中很多源碼都值得我們?nèi)パ芯繉W(xué)習(xí),不能因?yàn)樗鼮槲覀兲峁┝撕啽愕氖褂梅绞?,我們就不去了解學(xué)習(xí)了。
定義
觀察者模式定義了對(duì)象之間的一對(duì)多依賴,這樣一來,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴者都會(huì)收到通知并自動(dòng)更新。
三. 橋接模式
橋接模式是一個(gè)很難理解的模式,夜也深了,這個(gè)模式會(huì)用例子來解釋,但就不寫代碼了。
小時(shí)候大家一定學(xué)過畫畫,我記得小時(shí)候有一種一套的畫筆,里面會(huì)包含一二十種畫筆,每種顏色不同,小時(shí)候沒覺得什么,但是從程序猿的角度再來思考一下這個(gè)東西。
- 畫筆可以有很多種類 軟毛、硬毛
- 畫筆還會(huì)有多種顏色 赤橙黃綠青藍(lán)紫
現(xiàn)在讓我們從程序的角度抽象一下畫家的畫筆:畫筆的材質(zhì)、畫筆顏色
有這兩種可變特性,設(shè)計(jì)接口的話,是不是接口就應(yīng)該有獲取這兩個(gè)屬性的方法,接下來,根據(jù)這兩種特性進(jìn)行組合,然后編寫實(shí)現(xiàn)類的話,總共有14支筆,要寫14個(gè)類,what??如果再加一種毛的材料,我是不是還得再加7個(gè)類,這絕對(duì)不可能。
那么怎么辦?我們是不是可以把這兩種抽象進(jìn)行分離呢?
- 定義一個(gè)畫筆毛刷種類的接口
- 定義一個(gè)顏色接口
然后再使用畫筆的時(shí)候在對(duì)這兩種屬性進(jìn)行畫筆的組合,是不是我們就可以做到只有9個(gè)類就行了,而就算任意增加畫筆材質(zhì),或者增加顏色,也只增加一個(gè)類。
所以現(xiàn)實(shí)中我們知道那些畫畫的人總是有畫筆,還有額外的顏料。
而這種把抽象進(jìn)行分離解耦的方式就是橋接模式。
定義
橋接模式即將抽象部分與它的實(shí)現(xiàn)部分分離開來,使他們都可以獨(dú)立變化。
這句話以前一直很不容易理解,現(xiàn)在可以試著理解一下了,說回上面的畫筆,其中畫筆材質(zhì),還有畫筆顏色都是畫筆的抽象,通常來講會(huì)將這兩個(gè)抽象定義到一個(gè)接口里,但由于每一種抽象有可能會(huì)分開變化,所以我們將其中的部分抽象,比如顏色,分離出去,然后通過類的組合關(guān)聯(lián)的方式讓畫筆具有顏色的屬性,這就是抽象部分和實(shí)現(xiàn)部分進(jìn)行分離開來的意思。
套用《大話設(shè)計(jì)模式》里面的就是實(shí)現(xiàn)系統(tǒng)可能有多個(gè)角度分類,每一種角度都可能變化,那么把這種多角度分類給分離出來讓他們獨(dú)立變化,減少他們之間耦合。
橋接模式中的所謂脫耦,就是指在一個(gè)軟件系統(tǒng)的抽象化和實(shí)現(xiàn)化之間使用關(guān)聯(lián)關(guān)系(組合或者聚合關(guān)系)而不是繼承關(guān)系,從而使兩者可以相對(duì)獨(dú)立地變化,這就是橋接模式的用意。