2007年7月20日
2007年7月10日
常聽說這么一句話(大意是這樣):不必可以去套用設計模式,如果按照面向對象的基本原則編程,自然是優(yōu)雅的設計,即使沒有刻意使用模式,設計也會近乎于模式。開始感覺有一點玄,但在看了《C#設計模式縱橫談》視頻后,覺得有所收獲。下面,就參考視頻的內容,嘗試著寫這么一個過程:根據面向對象的一般原則對設計進行重構,逐漸演化出觀察者模式。
涉及的面向對象設計原則:單一職責原則、封裝變化、面向接口編程、依賴倒置原則、開閉原則。
1.發(fā)布訂閱模型:

假如有需求如下:
銀行需要把帳戶的如匯款、轉賬或取款等操作通知用戶,途徑包括手機短信、 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è)務邏輯后,由email和phone通知用戶。
2.初步重構
好像有bad smells,恩,根據單一職責原則。新增Email類和Phone類,并把相關業(yè)務邏輯改到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.擁抱變化
這個解決方案有問題嗎?可能沒有問題。它實現了我們的需求:在帳戶有操作變動的時候,通知Email和Mobile去發(fā)送信息給用戶。但這樣設計就足夠了嗎?可能足夠了,可能還不夠。
考慮如下兩種情況:
1.在很長一段時間里,訂閱方式很穩(wěn)定,比如系統(tǒng)只通過郵件和手機短信進行信息訂閱,那么這個實現沒有太大問題;
2.在近一兩年或更短的時間,更多的訂閱方式將會源源不斷地被加進來:比如可以登錄官方網站等等,那這個實現就有問題:再看一下我們的UML圖,類BankAccount依賴于Email和Mobile類!就是說,如果需要添加新的訂閱方式ATM類的process()方法勢必要重新設計!
于是我們的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()方法也必須有相應的變化!這當然是種災難。我們必須改變這種情況。
先解決遺留問題:第一種情況:訂閱方式相對穩(wěn)定的情況下呢?不改動會產生災難嗎?
個人認為:不會。比如某個系統(tǒng)信息只通過手機短信訂閱,那就沒有必要太在意這個問題。考慮周全一點不好嗎,如果將來有類似需求呢?小心過度設計!為了將來可能出現需求而進行的預先設計并不太好。有需求,才有設計。
現在來看解決之道:
運用面向對象的思想,抽象出問題所在。BankAccount類依賴于 Email類和Mobile類,而Email和Mobile是具體的類,ATM依賴于具體的類了,而且還不止一個!回憶一下依賴倒置原則:具體應該依賴于抽象,底層模式應該依賴于高層模式。那怎么實現依賴倒置原則呢?面向對象編程中有一條總的原則:封裝變化。如何實現封裝變化?需要我們這樣:面向接口編程。
回顧一下:我們在設計中實現類依賴了具體的類,違反了依賴倒置原則。為了遵循依賴倒置原則,我們采用面向接口編程的方法,從而實現了面向對象的一條總的原則:封裝變化。
看代碼:
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圖:
現在,BankAccount依賴于interface AccountObserver。Email和Mobile實現AccountObserver接口。通過遵循面向接口編程遵循了依賴倒置原則。
4.開閉原則
終于修改好了,我們解決了訂閱者變化的問題。但如果發(fā)布者也傾向于變化呢?這就牽涉到面向對象里的另一個原則:開閉原則。即:對擴展開放,對修改關閉。具體怎么做呢?通過抽象類,從抽象類繼承具體類。
看最終的代碼(只寫幾個關鍵的方法,全貌可看最后的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實現Observer 接口;
4.觀察者ConcreteObserver間接依賴于ConcreteSubject類。
如果要增加具體的觀察者,只要再實現Obsever接口即可,而被觀察方不需要做任何修改。而如果需要修改被觀察者,只要從Subject抽象類繼承即可。