總結(jié),記錄 |
|
|||
日歷
統(tǒng)計(jì)
導(dǎo)航常用鏈接留言簿(1)隨筆分類(5)隨筆檔案(2)收藏夾(1)Web Sites開一扇門搜索積分與排名
最新評論
閱讀排行榜評論排行榜 |
常聽說這么一句話(大意是這樣):不必可以去套用設(shè)計(jì)模式,如果按照面向?qū)ο蟮幕驹瓌t編程,自然是優(yōu)雅的設(shè)計(jì),即使沒有刻意使用模式,設(shè)計(jì)也會近乎于模
式。開始感覺有一點(diǎn)玄,但在看了《C#設(shè)計(jì)模式縱橫談》視頻后,覺得有所收獲。下面,就參考視頻的內(nèi)容,嘗試著寫這么一個過程:根據(jù)面向?qū)ο蟮囊话阍瓌t對設(shè)計(jì)進(jìn)行重構(gòu),逐漸演化出觀察者模式。
涉及的面 向?qū)ο笤O(shè)計(jì)原則:單一職責(zé)原則、封裝變化、面向接口編程、依賴倒置原則、開閉原則。 1.發(fā)布訂閱模型: 假如有需求如下: 銀行需要把帳戶的如匯款、轉(zhuǎn)賬或取款等操作通知用戶,途徑包括手機(jī)短信、 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機(jī)的 process()方法在處理完業(yè)務(wù)邏輯后,由email和phone通知用戶。 2.初步重構(gòu) 好像有bad smells,恩,根據(jù)單一職責(zé)原則。新增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.擁抱變化 這個解決方案有問題嗎?可能沒有問題。它實(shí) 現(xiàn)了我們的需求:在帳戶有操作變動的時候,通知Email和Mobile去發(fā)送信息給用戶。但這樣設(shè)計(jì)就足夠了嗎?可能足夠了,可能還不夠。 考慮如下兩種情況: 1.在很長一段時間里,訂閱方式很穩(wěn)定,比如系統(tǒng)只通過郵件和手機(jī)短信進(jìn)行信息訂閱,那么這個實(shí)現(xiàn)沒 有太大問題; 2.在近一兩年或更短的時間,更多的訂閱方式將會源源不斷地被加進(jìn)來:比如可以登錄官方網(wǎng)站等等,那這個實(shí)現(xiàn)就有問題: 再看一下我們的UML圖,類BankAccount依賴于Email和Mobile類!就是說,如果需要添加新的訂閱方式ATM類的process()方 法勢必要重新設(shè)計(jì)! 于是我們的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)的變化!這當(dāng)然是種災(zāi)難。 我們必須改變這種情況。 先解決遺留問題:第一種情況:訂閱方式相對穩(wěn)定的情況下呢?不改動會產(chǎn)生災(zāi)難嗎? 個人認(rèn) 為:不會。比如某個系統(tǒng)信息只通過手機(jī)短信訂閱,那就沒有必要太在意這個問題。考慮周全一點(diǎn)不好嗎,如果將來有類似需求呢?小心過度設(shè)計(jì)!為了將來可能出現(xiàn)需求而進(jìn)行的預(yù)先設(shè)計(jì)并不太好。有需求,才有設(shè)計(jì)。 現(xiàn)在來看解決之道: 運(yùn)用面向?qū)ο蟮乃枷耄橄蟪鰡栴}所在。BankAccount類依賴于 Email類和Mobile類,而Email和Mobile是具體的類,ATM依賴于具體的類了,而且還不止一個!回憶一下依賴倒置原則:具體應(yīng)該依賴于抽象,底層模式應(yīng)該依賴于高層模式。那怎么實(shí)現(xiàn)依賴倒置原則呢?面向?qū)ο缶幊讨杏幸粭l總的原則:封裝變化。如何實(shí)現(xiàn)封裝變化?需要我們這樣:面向接口編程。 回顧一下:我們在設(shè)計(jì)中實(shí)現(xiàn)類依賴了具體的類,違反了依賴倒置原則。為了遵循依賴倒置原則,我們采用面向接口編程的方法,從而 實(shí)現(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實(shí)現(xiàn)AccountObserver接口。通過遵循面向接口編程遵循了依賴倒置原則。 4.開閉原則 終于修改好了,我們解決了訂閱者變化的問 題。但如果發(fā)布者也傾向于變化呢?這就牽涉到面向?qū)ο罄锏牧硪粋€原則:開閉原則。即:對擴(kuò)展開放,對修改關(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實(shí)現(xiàn)Observer 接口; 4.觀察者ConcreteObserver間接依賴于ConcreteSubject類。 如果要增加具體的觀察者,只要再實(shí)現(xiàn)Obsever接口即可,而被觀察方不需要做任何修改。而如果需要修改被觀察者,只要從Subject抽 象類繼承即可。
評論:
|
![]() |
|
Copyright © L.X | Powered by: 博客園 模板提供:滬江博客 |