的內(nèi)容,嘗試著寫這么一個過程:根據(jù)面向?qū)ο蟮囊话阍瓌t對設(shè)計進行重構(gòu),逐漸演化出觀察者模式。
涉及的面向?qū)ο笤O(shè)計原則:單一職責原則、封裝變化、面向接口編程、依賴倒置原則、開閉原則。
1.發(fā)布訂閱模型:

假如有需求如下:
銀行需要把帳戶的如匯款、轉(zhuǎn)賬或取款等操作通知用戶,途徑包括手機短信、 email等。如圖所式。
自然地,我們可以這樣做:
public class ATM
{
BankAccount bankAccount;
public void process()
{
//bankAccount...
this.sendEmail(userEmail);
this.sendPhone(phoneNumber);
}
private void sendEmail(String userEmail)
{
//
}
private void sendMobile(String phoneNumber)
{
//
}
}
ATM機的 process()方法在處理完業(yè)務(wù)邏輯后,由email和phone通知用戶。
2.初步重構(gòu)
好像有bad smells,恩,根據(jù)單一職責原則。新增Email類和Phone類,并把相關(guān)業(yè)務(wù)邏輯改到BankAccount類完成。于是我們的代碼可以這樣:
public class ATM
{
BankAccount bankAccount;
public void process()
{
//
bankAccount.withDraw();
}
}
public class BankAccount
{
Email email;
Mobile mobile;
public void withDraw()
{
//
email.sendEmail(userEmail);
mobile.sendMobile(phoneNumber);
}
}
public class Email
{
public void sendEmail(String userEmail)
{
}
}
public class Mobile
{
public void sendMobile(String phoneNumber)
{
}
}
下面是代碼的UML圖:

3.擁抱變化
這個解決方案有問題嗎?可能沒有問題。它實現(xiàn)了我們的需求:在帳戶有操作變動的時候,通知Email和Mobile去發(fā)送信息給用戶。但這樣設(shè)計就足夠了嗎?可能足夠了,可能還不夠。
考慮如下兩種情況:
1.在很長一段時間里,訂閱方式很穩(wěn)定,比如系統(tǒng)只通過郵件和手機短信進行信息訂閱,那么這個實現(xiàn)沒有太大問題;
2.在近一兩年或更短的時間,更多的訂閱方式將會源源不斷地被加進來:比如可以登錄官方網(wǎng)站等等,那這個實現(xiàn)就有問題:再看一下我們的UML圖,類BankAccount依賴于Email和Mobile類!就是說,如果需要添加新的訂閱方式ATM類的process()方法勢必要重新設(shè)計!
于是我們的BankAccount類不得不變成:
public class BankAccount
{
Email email;
Mobile mobile;
Web web;
public void withDraw()
{
//
email.sendEmail(userEmail);
mobile.sendMobile(phoneNumber);
web.sendWeb(webSite);
}
}
如果還有另一種方式,那么process()方法就又會需要加入:otherSubscribe.send...();等方法,另外如果訂閱類的接口(這里指sendEmail等方法)發(fā)生變化,BankAccount的withDraw()方法也必須有相應(yīng)的變化!這當然是種災(zāi)難。我們必須改變這種情況。
先解決遺留問題:第一種情況:訂閱方式相對穩(wěn)定的情況下呢?不改動會產(chǎn)生災(zāi)難嗎?
個人認為:不會。比如某個系統(tǒng)信息只通過手機短信訂閱,那就沒有必要太在意這個問題??紤]周全一點不好嗎,如果將來有類似需求呢?小心過度設(shè)計!為了將來可能出現(xiàn)需求而進行的預(yù)先設(shè)計并不太好。有需求,才有設(shè)計。
現(xiàn)在來看解決之道:
運用面向?qū)ο蟮乃枷耄橄蟪鰡栴}所在。BankAccount類依賴于 Email類和Mobile類,而Email和Mobile是具體的類,ATM依賴于具體的類了,而且還不止一個!回憶一下依賴倒置原則:具體應(yīng)該依賴于抽象,底層模式應(yīng)該依賴于高層模式。那怎么實現(xiàn)依賴倒置原則呢?面向?qū)ο缶幊讨杏幸粭l總的原則:封裝變化。如何實現(xiàn)封裝變化?需要我們這樣:面向接口編程。
回顧一下:我們在設(shè)計中實現(xiàn)類依賴了具體的類,違反了依賴倒置原則。為了遵循依賴倒置原則,我們采用面向接口編程的方法,從而實現(xiàn)了面向?qū)ο蟮囊粭l總的原則:封裝變化。
看代碼:
public interface AccountObserver
{
public void upDate(UserAccount userAccount);
}
public class Email implements AccountObserver
{
public void upDate(UserAccount userAccount)
{
}
}
public class Mobile
{
public void upDate(UserAccount userAccount)
{
}
}
public class BankAccount
{
List <AccountObserver> observer = new ArrayList<AccountObserver>;
public void withDraw()
{
//
for (AccountObserver ao : observer)
{
ao.upDate(userAccount)
}
}
public void addOberver(AccountObserver accountObserver)
{
observer.add(accountObserver);
}
}
UML圖:
現(xiàn)在,BankAccount依賴于interface AccountObserver。Email和Mobile實現(xiàn)AccountObserver接口。通過遵循面向接口編程遵循了依賴倒置原則。
4.開閉原則
終于修改好了,我們解決了訂閱者變化的問題。但如果發(fā)布者也傾向于變化呢?這就牽涉到面向?qū)ο罄锏牧硪粋€原則:開閉原則。即:對擴展開放,對修改關(guān)閉。具體怎么做呢?通過抽象類,從抽象類繼承具體類。
看最終的代碼(只寫幾個關(guān)鍵的方法,全貌可看最后的UML圖):
訂閱:
public interface AccountObserver
{
public void upDate(UserAccount userAccount);
}
public class Email implements AccountObserver
{
public void upDate(UserAccount userAccount)
{
}
}
public class Mobile implements AccountObserver
{
public void upDate(UserAccount userAccount)
{
}
}
發(fā)布:
public abstract class Subject
{
List <AccountObserver> observer = new ArrayList<AccountObserver>;
protected void withDraw()
{
//
notify();
}
protected void notify(UserAccount userAccount)
{
for (AccountObserver ao : observer)
{
ao.upDate(userAccount)
}
}
protected void addOberver(AccountObserver accountObserver)
{
observer.add(accountObserver);
}
protected void deleteOberver(AccountObserver accountObserver)
{
observer.remove(accountObserver);
}
}
public class BankAccount extends Subject
{
public void withDraw()
{
//
for (AccountObserver ao : observer)
{
ao.upDate(userAccount)
}
}
}
看UML圖:

5.觀察者模式概況
這就是觀察者模式了,對比一下官方的UML圖,是不是一目了然了呢?
稍作說明(這里的依賴都是指廣義的依賴):
1.被觀察者ConcreteSubject繼承自Subject抽象類;
2.Subject抽象類依賴于觀察者Observer抽象接口;
3.觀察者ConcreteObserver實現(xiàn)Observer 接口;
4.觀察者ConcreteObserver間接依賴于ConcreteSubject類。
如果要增加具體的觀察者,只要再實現(xiàn)Obsever接口即可,而被觀察方不需要做任何修改。而如果需要修改被觀察者,只要從Subject抽象類繼承即可。