設(shè)計(jì)模式問(wèn)答(二)

什么是解釋器模式?

解釋器模式允許將語(yǔ)義解釋到代碼解決方案中。下面讓我們來(lái)了解具體含義?語(yǔ)法是映射到類中來(lái)應(yīng)用到解決方案中,例如7-2可以映射到‘clsMinus’類。在第一行解釋器模式給我們提供了一種如何編寫解釋器的方案,該方案中解釋器可以讀取并在代碼中執(zhí)行該語(yǔ)法。例如以下例子中我們給出了一個(gè)日期格式化語(yǔ)法,其中解釋器給出了轉(zhuǎn)換的代碼解決方案并輸出期望結(jié)果。

圖1:日期語(yǔ)法

讓我們來(lái)寫一個(gè)如圖“日期語(yǔ)法”中所示的日期格式化解釋器。在開始之前我們需要理解解釋器模式中不同組件,之后我們將映射相同的語(yǔ)法。Context包含數(shù)據(jù),邏輯部分包含將context轉(zhuǎn)換為可讀格式的邏輯。

圖2:上下文及邏輯

接下來(lái)我們看看日期格式化中的語(yǔ)法。在定義任何語(yǔ)法之前,我們首先要把語(yǔ)法拆分為小的邏輯塊。圖“語(yǔ)法映射到類”展示了不同組件是如何確定的,然后映射到類的語(yǔ)法只是實(shí)現(xiàn)了完整語(yǔ)法中的一部分。這樣我們將日期格式化拆分為4個(gè)組件月、日、年以及分隔符;對(duì)這四個(gè)組件我們定義了不同的類來(lái)包含各自的邏輯,如圖“語(yǔ)法映射到類”中所示。因此,我們將為日期格式化中4個(gè)不同的組件創(chuàng)建不同的類。

圖3:語(yǔ)法映射到類

如前所述,有兩個(gè)類其中一個(gè)是表達(dá)式類,該類包含邏輯;另一個(gè)是上下文類(context),該類包含數(shù)據(jù),如圖“表達(dá)式和上下文類”所示。我們將不同的解析定義在不同的類中,所有這些類都繼承自‘ClsAbstractExpression’接口,該接口包含‘Evaluate’方法;其中‘Evaluate’方法持有一個(gè)包含有數(shù)據(jù)的上下文類,并且通過(guò)表達(dá)式邏輯來(lái)解析數(shù)據(jù)。例如‘ClsYearExpression’會(huì)將‘YYYY’替換為年份,’ClsMonthExpression’將‘MM’替換為月份等等。

圖4:解釋器模式類圖

圖5:表達(dá)式和上下文類

至此我們已經(jīng)將表達(dá)式解析邏輯封裝在了不同的類中,現(xiàn)在我們來(lái)看下客戶端使用邏輯。客戶端首先傳遞日期格式化語(yǔ)法到上下文類;根據(jù)日期格式化我們添加表示類到一個(gè)集合中。例如當(dāng)發(fā)現(xiàn)“DD”表達(dá)式時(shí)會(huì)增加‘ClsDayExpression’,遇到‘MM’表達(dá)式時(shí)則會(huì)添加‘ClsMonthExpression’類,等等。最終我們循環(huán)調(diào)用這些類的‘Evaluate’方法。當(dāng)所有類都被調(diào)用后我們就得到了顯示結(jié)果。

圖6:客戶端效用解釋器代碼邏輯

什么是迭代器模式?

迭代器模式允許在不暴露內(nèi)部代碼的情況下順序存取元素。具體含義如下。假設(shè)有一個(gè)記錄的集合,我們想順序性瀏覽這些結(jié)合同時(shí)還想保存當(dāng)前已經(jīng)瀏覽的記錄的位置,那么迭代器模式就是我們所需要的。該模式是最常見且經(jīng)常無(wú)意中使用的模式。在所有使用‘foreach’(允許順序性循環(huán)一個(gè)集合)循環(huán)的地方我們就在一定程度上在使用迭代器模式了。

圖7:迭代器模式業(yè)務(wù)邏輯

在圖“迭代器模式業(yè)務(wù)邏輯”中我們定義了一個(gè)包含客戶類集合的類‘clsIterator’。因此我們我們首先在‘clsIterator’中定義了一個(gè)ArrayList以及一個(gè)‘FillObjects’方法來(lái)為ArrayList填充數(shù)據(jù)。這個(gè)消費(fèi)者集合ArrayList是私有的(private),并且客戶數(shù)據(jù)可以通過(guò)集合中的下標(biāo)來(lái)查找。因此我們開放了‘getByIndex’方法(通過(guò)指定下標(biāo)來(lái)查找元素)、‘Prev’方法(獲得集合中前一個(gè)元素)、‘Next’方法(獲取集合中下一個(gè)元素)、‘getFirst’方法(獲取結(jié)合中第一個(gè)元素)、‘getLast’方法(獲取集合中最后一個(gè)元素)。

客戶端只能看到這些方法,通過(guò)這些函數(shù)來(lái)順序訪問(wèn)集合并且可以記住已經(jīng)訪問(wèn)過(guò)的下標(biāo)。

下圖“客戶端使用迭代器模式邏輯”展示了如何使用‘clsIterator’中next、previous、last、first函數(shù)以及指定下標(biāo)來(lái)創(chuàng)建‘ObjIterator’對(duì)象的。

圖8:客戶端使用迭代器模式邏輯

什么是調(diào)停者模式?

很多時(shí)候項(xiàng)目中組件之間的通信是非常復(fù)雜的,因?yàn)榻M件之間的相互調(diào)用而變的復(fù)雜。調(diào)停者模式幫助對(duì)象以沒有關(guān)聯(lián)的方式來(lái)通信,這使得復(fù)雜度最小化。

圖9:調(diào)停者模式簡(jiǎn)單例子

讓我們考慮上圖“調(diào)停者模式簡(jiǎn)單例子”中的情況,該圖描述了一個(gè)真實(shí)場(chǎng)景中需要調(diào)停者模式的情況。它是一個(gè)非常用戶友好的接口;它有3中典型場(chǎng)景:

場(chǎng)景1:當(dāng)用戶在文本框中輸入文字后需要開啟增加按鈕以及清空按鈕。在這個(gè)例子中文本框中為空,此時(shí)需要禁用增加和清空按鈕。

圖10:場(chǎng)景1

場(chǎng)景2:當(dāng)用戶點(diǎn)擊添加按鈕時(shí)數(shù)據(jù)應(yīng)該進(jìn)入到列表框中。一旦輸入內(nèi)容進(jìn)入列表框,系統(tǒng)需要清空文本框并禁用增加和清除按鈕。

圖11:場(chǎng)景2

場(chǎng)景3:如果用戶點(diǎn)擊清除按鈕系統(tǒng)應(yīng)該清空文本框并禁用增加和清除按鈕。

圖12:場(chǎng)景3

從上圖場(chǎng)景3中UI界面可以看出這些UI之間的交互是多么復(fù)雜。下圖“組件之間的復(fù)雜交互”展示了邏輯的復(fù)雜性。

圖13:組件之間的復(fù)雜交互

接著我們來(lái)看下圖“調(diào)停者使用簡(jiǎn)化圖”。各個(gè)組件之間并不是直接交互,而是都與一個(gè)中心化組件如調(diào)停者交互,由調(diào)停者來(lái)關(guān)注發(fā)送信息至其他組件,這樣邏輯將變的干凈清晰。

圖14:調(diào)停者使用簡(jiǎn)化圖

下面我們看看代碼是如何寫的。我們將使用C#但是大家也可以很容易的將其轉(zhuǎn)換為java代碼或者其他語(yǔ)言。下圖“調(diào)停者類”展示的調(diào)停者類的完整代碼。

調(diào)停者類首先做的事情就是持有復(fù)雜交互相關(guān)類的引用。所以這里我們開放3個(gè)名為‘Register’的重載方法;‘Register’持有文本框?qū)ο蠛桶粹o對(duì)象;場(chǎng)景的交互被集中在‘ClickAddButton’、’TextChange’、‘ClickClearButton’方法中,這些方法將根據(jù)不同場(chǎng)景啟用和禁用相關(guān)UI。

圖15:調(diào)停者類

同時(shí)客戶端代碼非常整潔。在構(gòu)造函數(shù)中首先注冊(cè)所有需要和調(diào)停者類交互的組件;接著對(duì)于每個(gè)場(chǎng)景只需要調(diào)用調(diào)停者類的方法即可。簡(jiǎn)而言之,當(dāng)文本發(fā)生變化時(shí)我們調(diào)用調(diào)停者類的‘TextChange’方法,當(dāng)用戶點(diǎn)擊添加按鈕時(shí)調(diào)用‘ClickAddButton’方法,當(dāng)用戶點(diǎn)擊清空按鈕時(shí)調(diào)用‘ClickClearButton’方法。

圖16:調(diào)停者模式客戶端邏輯

什么是備忘錄模式?

備忘錄模式是提供一種在不破壞封裝的情況下提供捕獲對(duì)象內(nèi)部狀態(tài)的方式。它有助于存儲(chǔ)對(duì)象的快照以便任何時(shí)候查詢。下面我們來(lái)了解它在實(shí)際場(chǎng)景中的含義。下圖“備忘錄模式實(shí)踐例子”中展示的了一個(gè)客戶屏幕;假設(shè)用戶開始編輯一條客戶記錄并作出一些修改;過(guò)了一會(huì)以后該用戶覺得操作有誤想要恢復(fù)到原始數(shù)據(jù),這就是備忘錄模式的適用場(chǎng)景。它幫助我們存儲(chǔ)一份數(shù)據(jù)的拷貝并在用戶點(diǎn)擊取消時(shí)恢復(fù)到原始狀態(tài)。

圖17:備忘錄模式實(shí)踐例子

下面我們來(lái)嘗試使用C#完成我們剛剛討論的客戶UI的例子。以下是客戶類‘clsCustomer’,該類聚合了存儲(chǔ)數(shù)據(jù)快照的備忘錄類‘clsCustomerMemento’。其中備忘錄類‘clsCustomerMemento’是明確用來(lái)復(fù)制客戶類(不包括方法)‘clsCustomer’;當(dāng)消費(fèi)者類‘clsCustomer’初始化時(shí),備忘錄類也跟著初始化;當(dāng)客戶類數(shù)據(jù)發(fā)生改變時(shí),備忘錄類中的快照未發(fā)生變化;其中‘Revert’方法可以將備忘錄中的數(shù)據(jù)恢復(fù)到main類中。

圖18:備忘錄中的客戶類

客戶端代碼非常簡(jiǎn)單。創(chuàng)建客戶類,一旦有問(wèn)題我們可以點(diǎn)擊取消按鈕來(lái)調(diào)用‘revert’方法將數(shù)據(jù)恢復(fù)至備忘錄中的快照數(shù)據(jù)。圖“備忘錄客戶端代碼”圖中展示了該邏輯。

圖19:備忘錄客戶端代碼

什么是觀察者模式?

觀察者模式幫助我們完成父類和與他相關(guān)或依賴的類之間的通信。其中有兩個(gè)比較重要的概念‘Subject’(主題)和‘Observers’(觀察者)。主題(subject)發(fā)送通知后,如果觀察者注冊(cè)到該主題,則會(huì)收到通知。下圖“subject和observers”展示了應(yīng)用(主題)發(fā)送通知到所有觀察者(email、事件日志、SMS)。我們也可以將這個(gè)例子封裝成發(fā)布/訂閱模型;發(fā)布者就是應(yīng)用,訂閱者就是email、事件日志、sms。

圖20:subject和observers

接下來(lái)我們嘗試我們?cè)谇耙恍」?jié)中定義的簡(jiǎn)單示例代碼。首先,我們先看看訂閱者/通知類。圖“訂閱者類”展示了該代碼,其中為所有訂閱者定義了統(tǒng)一接口如‘INotification’,該接口有一個(gè)‘notify’方法。這個(gè)接口被所有具體的通知類實(shí)現(xiàn),所有通知類定義自己的通知方法。對(duì)當(dāng)前的場(chǎng)景來(lái)說(shuō)我們只是顯示一個(gè)打印字符串來(lái)表示某個(gè)特定方法被執(zhí)行。

圖21:訂閱者類

如前所示,觀察者模式中分為兩個(gè)部分,一個(gè)是觀察者/訂閱者,如前邊我們提到的一樣;另一個(gè)是發(fā)布者或者主題。

發(fā)布者有一個(gè)集合來(lái)存放所有需要收到通知的訂閱者。通過(guò)使用‘a(chǎn)ddNotification’和‘removeNotification’可以從集合中增加或刪除訂閱者;使用‘NotifyAll’方法循環(huán)所有訂閱者來(lái)發(fā)送通知。

圖22:發(fā)布者/主題類

至此我們已經(jīng)了解了發(fā)布致和訂閱者類,下面讓我們來(lái)些客戶端代碼并看看觀察者表現(xiàn)。下面是一個(gè)觀察者客戶機(jī)代碼片段。首先創(chuàng)建一個(gè)擁有訂閱者集合的通知者對(duì)象,接著將需要被通知的訂閱者添加到結(jié)合中。此時(shí)的邏輯是如果客戶的代碼長(zhǎng)度超過(guò)10個(gè)字符時(shí)通知所有訂閱者。

圖24:觀察者模式客戶端代碼

1. 本文由程序員學(xué)架構(gòu)翻譯

2. 本文譯自

http://www.codeproject.com/Articles/28359/Design-pattern-FAQ-Part-Design-pattern-training

3. 轉(zhuǎn)載請(qǐng)務(wù)必注明本文出自:程序員學(xué)架構(gòu)(微信號(hào):archleaner)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容