????????從今天起,我們開始學(xué)習(xí)行為型設(shè)計(jì)模式。我們知道,創(chuàng)建型設(shè)計(jì)模式主要解決“對(duì)象的創(chuàng)建”問題,結(jié)構(gòu)型設(shè)計(jì)模式主要解決“類或?qū)ο蟮慕M合或組裝”問題,那行為型設(shè)計(jì)模式主要解決的就是“類或?qū)ο笾g的交互”問題。
原理及應(yīng)用場(chǎng)景剖析
在對(duì)象之間定義一個(gè)一對(duì)多的依賴,當(dāng)一個(gè)對(duì)象狀態(tài)改變的時(shí)候,所有依賴的對(duì)象都會(huì)自動(dòng)收到通知。
一般情況下,被依賴的對(duì)象叫作被觀察者(Observable),依賴的對(duì)象叫作觀察者(Observer)?.很多書籍或資料給出的最常見的實(shí)現(xiàn)方式。具體的代碼如下所示:
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
public interface Observer {
void update(Message message);
}
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
//TODO: 獲取消息通知,執(zhí)行自己的邏輯...
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
//TODO: 獲取消息通知,執(zhí)行自己的邏輯...
System.out.println("ConcreteObserverTwo is notified.");
}
}
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
????????原理和代碼實(shí)現(xiàn)都非常簡(jiǎn)單,也比較好理解,不需要我過多的解釋。我們還是通過一個(gè)具體的例子來重點(diǎn)講一下,什么情況下需要用到這種設(shè)計(jì)模式?或者說,這種設(shè)計(jì)模式能解決什么問題呢?
實(shí)際例子:
????????假設(shè)我們?cè)陂_發(fā)一個(gè)P2P投資理財(cái)系統(tǒng),用戶注冊(cè)成功之后,我們會(huì)給用戶發(fā)放投資體驗(yàn)金。代碼實(shí)現(xiàn)大致是下面這個(gè)樣子的:
public class UserController {
private UserService userService; // 依賴注入
private PromotionService promotionService; // 依賴注入
public Long register(String telephone, String password) {
//省略輸入?yún)?shù)的校驗(yàn)代碼
//省略u(píng)serService.register()異常的try-catch代碼
long userId = userService.register(telephone, password);
promotionService.issueNewUserExperienceCash(userId);
return userId;
}
}
雖然注冊(cè)接口做了兩件事情,注冊(cè)和發(fā)放體驗(yàn)金,違反單一職責(zé)原則,但是,如果沒有擴(kuò)展和修改的需求,現(xiàn)在的代碼實(shí)現(xiàn)是可以接受的。
如果非得用觀察者模式,就需要引入更多的類和更加復(fù)雜的代碼結(jié)構(gòu),反倒是一種過度設(shè)計(jì)。
但是項(xiàng)目唯一不變的就是變化。?
比如,用戶注冊(cè)成功之后,不再發(fā)放體驗(yàn)金,而是改為發(fā)放優(yōu)惠券,并且還要給用戶發(fā)送一封“歡迎注冊(cè)成功”的站內(nèi)信。這種情況下,我們就需要頻繁地修改register()函數(shù)中的代碼,違反開閉原則。而且,如果注冊(cè)成功之后需要執(zhí)行的后續(xù)操作越來越多,那register()函數(shù)的邏輯會(huì)變得越來越復(fù)雜,也就影響到代碼的可讀性和可維護(hù)性。
這個(gè)時(shí)候,觀察者模式就能派上用場(chǎng)了。利用觀察者模式,我對(duì)上面的代碼進(jìn)行了重構(gòu)。重構(gòu)之后的代碼如下所示:
public interface RegObserver {
void handleRegSuccess(long userId);
}
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依賴注入
@Override
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver implements RegObserver {
private NotificationService notificationService;
@Override
public void handleRegSuccess(long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
public class UserController {
private UserService userService; // 依賴注入
private List<RegObserver> regObservers = new ArrayList<>();
// 一次性設(shè)置好,之后也不可能動(dòng)態(tài)的修改
public void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
//省略輸入?yún)?shù)的校驗(yàn)代碼
//省略u(píng)serService.register()異常的try-catch代碼
long userId = userService.register(telephone, password);
for (RegObserver observer : regObservers) {
observer.handleRegSuccess(userId);
}
return userId;
}
}
當(dāng)我們需要添加新的觀察者的時(shí)候,比如,用戶注冊(cè)成功之后,推送用戶注冊(cè)信息給大數(shù)據(jù)征信系統(tǒng),基于觀察者模式的代碼實(shí)現(xiàn),UserController類的register()函數(shù)完全不需要修改,只需要再添加一個(gè)實(shí)現(xiàn)了RegObserver接口的類,并且通過setRegObservers()函數(shù)將它注冊(cè)到UserController類中即可。
不過,你可能會(huì)說,當(dāng)我們把發(fā)送體驗(yàn)金替換為發(fā)送優(yōu)惠券的時(shí)候,需要修改RegPromotionObserver類中handleRegSuccess()函數(shù)的代碼,這還是違反開閉原則呀?你說得沒錯(cuò),不過,相對(duì)于register()函數(shù)來說,handleRegSuccess()函數(shù)的邏輯要簡(jiǎn)單很多,修改更不容易出錯(cuò),引入bug的風(fēng)險(xiǎn)更低。
有時(shí)候設(shè)計(jì)模式可以進(jìn)行解耦,大大化簡(jiǎn)運(yùn)維的復(fù)雜度。但是不會(huì)消除所有的復(fù)雜度,不一定能夠滿足所有的設(shè)計(jì)原則。?
?
基于不同應(yīng)用場(chǎng)景的不同實(shí)現(xiàn)方式
觀察者模式的應(yīng)用場(chǎng)景非常廣泛,小到代碼層面的解耦,大到架構(gòu)層面的系統(tǒng)解耦,再或者一些產(chǎn)品的設(shè)計(jì)思路,都有這種模式的影子,比如,郵件訂閱、RSS Feeds,本質(zhì)上都是觀察者模式。
不同的應(yīng)用場(chǎng)景和需求下,這個(gè)模式也有截然不同的實(shí)現(xiàn)方式,開篇的時(shí)候我們也提到,有同步阻塞的實(shí)現(xiàn)方式,也有異步非阻塞的實(shí)現(xiàn)方式;有進(jìn)程內(nèi)的實(shí)現(xiàn)方式,也有跨進(jìn)程的實(shí)現(xiàn)方式。
之前講到的實(shí)現(xiàn)方式,從剛剛的分類方式上來看,它是一種同步阻塞的實(shí)現(xiàn)方式。觀察者和被觀察者代碼在同一個(gè)線程內(nèi)執(zhí)行,被觀察者一直阻塞,直到所有的觀察者代碼都執(zhí)行完成之后,才執(zhí)行后續(xù)的代碼。
如果注冊(cè)接口是一個(gè)調(diào)用比較頻繁的接口,對(duì)性能非常敏感,希望接口的響應(yīng)時(shí)間盡可能短,那我們可以將同步阻塞的實(shí)現(xiàn)方式改為異步非阻塞的實(shí)現(xiàn)方式,以此來減少響應(yīng)時(shí)間。具體來講,當(dāng)userService.register()函數(shù)執(zhí)行完成之后,我們啟動(dòng)一個(gè)新的線程來執(zhí)行觀察者的handleRegSuccess()函數(shù),這樣userController.register()函數(shù)就不需要等到所有的handleRegSuccess()函數(shù)都執(zhí)行完成之后才返回結(jié)果給客戶端。userController.register()函數(shù)從執(zhí)行3個(gè)SQL語句才返回,減少到只需要執(zhí)行1個(gè)SQL語句就返回,響應(yīng)時(shí)間粗略來講減少為原來的1/3。
剛剛講到的兩個(gè)場(chǎng)景,不管是同步阻塞實(shí)現(xiàn)方式還是異步非阻塞實(shí)現(xiàn)方式,都是進(jìn)程內(nèi)的實(shí)現(xiàn)方式。如果用戶注冊(cè)成功之后,我們需要發(fā)送用戶信息給大數(shù)據(jù)征信系統(tǒng),而大數(shù)據(jù)征信系統(tǒng)是一個(gè)獨(dú)立的系統(tǒng),跟它之間的交互是跨不同進(jìn)程的,那如何實(shí)現(xiàn)一個(gè)跨進(jìn)程的觀察者模式呢?
是否要基于RPC框架調(diào)用,或者利用消息隊(duì)列進(jìn)行處理。
反思與總結(jié):文章來源:http://www.zghlxwxcb.cn/news/detail-571170.html
觀察者模式的應(yīng)用場(chǎng)景非常廣泛,小到代碼層面的解耦,大到架構(gòu)層面的系統(tǒng)解耦,再或者一些產(chǎn)品的設(shè)計(jì)思路,都有這種模式的影子,比如,郵件訂閱、RSS Feeds,本質(zhì)上都是觀察者模式。不同的應(yīng)用場(chǎng)景和需求下,這個(gè)模式也有截然不同的實(shí)現(xiàn)方式,有同步阻塞的實(shí)現(xiàn)方式,也有異步非阻塞的實(shí)現(xiàn)方式;有進(jìn)程內(nèi)的實(shí)現(xiàn)方式,也有跨進(jìn)程的實(shí)現(xiàn)方式。文章來源地址http://www.zghlxwxcb.cn/news/detail-571170.html
到了這里,關(guān)于觀察者模式(上):詳解各種應(yīng)用場(chǎng)景下觀察者模式的不同實(shí)現(xiàn)方式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!