0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

應(yīng)用面向?qū)ο缶幊蘏oC原則的典型示例

AGk5_ZLG_zhiyua ? 來(lái)源:互聯(lián)網(wǎng) ? 作者:佚名 ? 2018-02-05 09:36 ? 次閱讀

周立功教授新書(shū)《面向AMetal框架與接口編程(上)》,對(duì)AMetal框架進(jìn)行了詳細(xì)介紹,通過(guò)閱讀這本書(shū),你可以學(xué)到高度復(fù)用的軟件設(shè)計(jì)原則和面向接口編程的開(kāi)發(fā)思想,聚焦自己的“核心域”,改變自己的編程思維,實(shí)現(xiàn)企業(yè)和個(gè)人的共同進(jìn)步。經(jīng)周立功教授授權(quán),即日起,致遠(yuǎn)電子公眾號(hào)將對(duì)該書(shū)內(nèi)容進(jìn)行連載,愿共勉之。

第九章為BLE&zigbee 無(wú)線模塊,本文內(nèi)容為9.3 MVC 框架。

>>> 9.3.1 MVC 模式

模型-視圖-控制器(Model-View-Controller,MVC)模式是應(yīng)用面向?qū)ο缶幊?SoC 原則的典型示例,模式的名稱來(lái)自用于切分軟件應(yīng)用的三個(gè)主要部分,即模型部分、視圖部分和控制器部分。它是 Smalltalk 中的用戶界面框架,其目的是將模型從用戶界面解耦。因?yàn)?Model相對(duì)來(lái)說(shuō)比較穩(wěn)定,而 View 和 Controller 相對(duì)來(lái)說(shuō)容易變化,所以通過(guò)分層可以隔離變化。

而且視圖與模型的分離帶來(lái)的好處允許美工專心設(shè)計(jì) UI 部分,程序員專心開(kāi)發(fā)軟件,互相不會(huì)干擾。MVC 包括 3 類組件:

  • Model:模型代表應(yīng)用信息,負(fù)責(zé)“內(nèi)部實(shí)現(xiàn)”的具體功能,包含和管理(業(yè)務(wù))邏輯、數(shù)據(jù)、狀態(tài)以及應(yīng)用的規(guī)則,不依賴 UI;

  • View:通常在一個(gè)人機(jī)接口上呈現(xiàn) Model 信息的抽象視圖,即視圖是模型的外在表現(xiàn)——用戶界面的一部分,視圖只是展示數(shù)據(jù),但不處理數(shù)據(jù)。視圖并非一定是圖形化的,文本輸出也是視圖;

  • Controller:將用戶輸入分配到模型與視圖中去,控制器也是用戶界面的一部分,定義用戶界面對(duì)用戶輸入的響應(yīng)方式。

如圖 9.10 所示為 MVC 框架的示意圖,視圖和控制器合起來(lái)組成用戶界面,用戶界面包括輸入和輸出兩部分:視圖相當(dāng)于輸出部分——顯示結(jié)果給用戶,控制器相當(dāng)于輸入部分——響應(yīng)用戶的操作。這 3 類組件通過(guò)交互進(jìn)行協(xié)作,View創(chuàng)建 Controller 后,Controller 根據(jù)用戶交互調(diào)用Model 的相應(yīng)服務(wù)。而 Model 會(huì)將自身狀態(tài)的改變通知View,View則會(huì)讀取Model的信息更新自身。比如,當(dāng)用戶通過(guò)單擊(鍵入或觸摸等)某個(gè)按鈕觸發(fā)一個(gè)視圖時(shí),視圖將用戶操作告知控制器??刂破魈幚碛脩糨斎?,并與模型交互。模型執(zhí)行所有必要的校驗(yàn)和狀態(tài)改變,并通知控制器應(yīng)該做什么??刂破靼凑漳P徒o出的指令,指導(dǎo)視圖更新顯示內(nèi)容輸出。

圖 9.10 MVC 框架示意圖

通常 MVC 被認(rèn)為是一種框架模式,而不是一種設(shè)計(jì)模式,因?yàn)榭蚣苣J脚c設(shè)計(jì)模式之間的區(qū)別在于,前者比后者的范疇更廣泛。其主要特征在于它能夠?yàn)槎鄠€(gè)不同的視圖提供數(shù)據(jù),即同一個(gè)模型可以支持多個(gè)視圖,模型的代碼只需要寫(xiě)一次就可以被多個(gè)視圖重用。假設(shè)在兩個(gè)視圖中使用同一個(gè)模型的數(shù)據(jù),無(wú)論何時(shí)更改了模型,都需要更新兩個(gè)視圖,可以使用觀察者模式解決。

>>> 9.3.2 觀察者模式

觀察者模式定義了一對(duì)多的對(duì)象之間的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生變化時(shí),所有依賴于它的對(duì)象都會(huì)得到通知并自動(dòng)更新,因此觀察者模式是一種行為模式,其適用于根據(jù)觀察對(duì)象狀態(tài)進(jìn)行相應(yīng)處理的場(chǎng)景。

在溫度檢測(cè)儀中,當(dāng)溫度傳感器得到的值發(fā)生變化時(shí),希望視圖的內(nèi)容同步改變。雖然可以在溫度檢測(cè)代碼中附加更新顯示的功能,但在本質(zhì)上更新顯示與溫度檢測(cè)是完全不同的兩種處理方法,因此相互之間形成了高度依賴性的關(guān)系。

觀察者模式就是一種避免高度依賴性的方法,構(gòu)成觀察者模式的有兩個(gè)對(duì)象:發(fā)生變化的對(duì)象稱為 觀察對(duì)象(Subject),而被通知的對(duì)象稱為 觀察者(Observer)。如果觀察對(duì)象的狀態(tài)發(fā)生變化,則所有的觀察者都會(huì)收到消息,同步更新自己的狀態(tài),因此這種交互方式又被稱為“依賴”或“發(fā)布—訂閱”。雖然觀察對(duì)象是消息的發(fā)布者,但它發(fā)布消息時(shí)并不需要知道誰(shuí)是它的觀察者,因此觀察者的數(shù)量是不限的,即觀察對(duì)象維護(hù)了觀察者對(duì)象的結(jié)合?,F(xiàn)在的問(wèn)題是,如果觀察者與觀察對(duì)象互相引用,它們變得互相依賴,這可能會(huì)對(duì)一個(gè)系統(tǒng)的分層和重用性產(chǎn)生負(fù)面影響。基于此,觀察者模式通過(guò)定義一個(gè)接口通知觀察對(duì)象發(fā)生了變化,從而將觀察者與觀察對(duì)象解耦,只依賴于觀察者和觀察對(duì)象的抽象類,從而保證了訂閱系統(tǒng)的靈活性和可擴(kuò)展性。

圖 9.11 觀察者模式的實(shí)現(xiàn)結(jié)構(gòu)

在如圖 9.11 所示的觀察者模式的結(jié)構(gòu)圖中,觀察者類(Observer)、觀察對(duì)象類(Subject)、具體的觀察者類(ConcreteObserver)和具體的觀察對(duì)象類(ConcreteSubject)共同完成觀察者模式的各項(xiàng)職責(zé),使用“添加、通知和刪除”的方法實(shí)現(xiàn)觀察者模式。

  • Observer(觀察者):Observer 角色負(fù)責(zé)接收來(lái)自 Subject 角色狀態(tài)變化的通知,即Subject 調(diào)用每個(gè) Observer 的 update 方法,發(fā)消息通知所有的 Observer,從而將 Subject 和Observer 解耦。因此,當(dāng)對(duì)象間有數(shù)據(jù)依賴時(shí),最好用觀察者模式對(duì)它們解耦。

    由于 Observer 角色是抽象的,雖然它聲明了 update 方法,但不提供任何實(shí)現(xiàn),該方法在子類中實(shí)現(xiàn)。

  • Subject(觀察對(duì)象):Subject 角色表示觀察對(duì)象,定義觀察對(duì)象必須實(shí)現(xiàn)的職責(zé):管理(添加或刪除)觀察者并通知觀察者。從 Subject 指向 Observer 的箭頭線表明 Subject包含了 Observer 類型的實(shí)例,箭頭前面的實(shí)心圓圈表示多于一個(gè)實(shí)例。

    當(dāng)觀察對(duì)象的狀態(tài)變化時(shí),由于它不知道該將消息發(fā)送給誰(shuí),因此 Subject 角色定義了一個(gè)可以存儲(chǔ) N 個(gè)觀察者對(duì)象“列表”,以及添加(attach)、刪除(detach)和通知(notify)觀察者的抽象方法。

    當(dāng)觀察對(duì)象決定通知它的所有觀察者對(duì)象時(shí),notify 遍歷觀察者對(duì)象“列表”,調(diào)用每個(gè) Observer 的 update 方法,發(fā)消息通知所有的 Observer,告訴它們“我的狀態(tài)改變了,請(qǐng)更新顯示內(nèi)容”。如果在 Subject 角色中注冊(cè)了多個(gè) Observer 角色,誰(shuí)先注冊(cè)就先調(diào)用誰(shuí)的update 方法,不能改變調(diào)用順序。

  • ConcreteObserver(具體的觀察者):ConcreteObserver 角色表示具體的觀察者,當(dāng)它的方法被調(diào)用后,就會(huì)去獲取具體的觀察對(duì)象的最新狀態(tài)。從 ConcreteObserver 指向ConcreteSubject 的箭頭線表明 ConcreteObserver 包含了 ConcreteSubject 類型的實(shí)例,由于沒(méi)有實(shí)心圓圈,則表示有且僅有一個(gè)實(shí)例。

  • ConcreteSubject (具體的觀察對(duì)象):ConcreteSubject 角色表示具體的觀察對(duì)象,其職責(zé)非常明確——誰(shuí)能觀察,誰(shuí)不能觀察。它不僅提供函數(shù)的實(shí)現(xiàn),而且提供獲取和管理它發(fā)布數(shù)據(jù)的方法。當(dāng)自身的狀態(tài)發(fā)生改變時(shí),它會(huì)通知所有已經(jīng)注冊(cè)的 Observer 角色。除了要支持 Subject 角色外,根據(jù)業(yè)務(wù)的不同,可能還需要提供諸如 getState()、setState()這樣的函數(shù),用于具體的觀察者獲取或設(shè)置相關(guān)的狀態(tài)。

觀察者模式中的 Subject 和 Observer 接口是為了處理 Subject 的變化而設(shè)計(jì)的,因此當(dāng)對(duì)象之間有數(shù)據(jù)依賴時(shí),最好用觀察者模式對(duì)它們進(jìn)行解耦。觀察者模式的適用范圍如下:

  • 當(dāng)一個(gè)抽象模型有兩個(gè)方面時(shí),如果其中一個(gè)方面依賴于另一個(gè)方面,則只要將這兩者封裝在獨(dú)立的對(duì)象中,即可使它們各自獨(dú)立地改變和復(fù)用。

  • 當(dāng)改變一個(gè)對(duì)象需要同時(shí)改變其它對(duì)象時(shí),卻不知道具體有多少個(gè)對(duì)象有待改變。

  • 當(dāng)一個(gè)對(duì)象必須通知其它對(duì)象時(shí),而又無(wú)法預(yù)知其它對(duì)象是誰(shuí),而你不希望這些對(duì)象是緊耦合的。

注意事項(xiàng):

  • 在觀察者模式中,觀察對(duì)象通知了觀察者,而這個(gè)觀察者同時(shí)也是一個(gè)觀察對(duì)象,它會(huì)通知其它的觀察者。因而常常會(huì)產(chǎn)生過(guò)于復(fù)雜的設(shè)計(jì),并且使調(diào)試變得更加困難。當(dāng)遇到這種情況時(shí),Mediator(仲裁)模式可能會(huì)幫助我們改進(jìn)這類代碼。

    根據(jù)經(jīng)驗(yàn)建議,最多允許出現(xiàn)一個(gè)對(duì)象既是觀察者也是觀察對(duì)象,即消息最多轉(zhuǎn)發(fā)一次(兩次),否則邏輯關(guān)系就會(huì)比較復(fù)雜且難以維護(hù)。

  • 如果觀察者比較多,且處理時(shí)間比較長(zhǎng),雖然可以使用異步處理方式,但要考慮線程安全和隊(duì)列問(wèn)題。

  • 當(dāng)觀察者與觀察對(duì)象的關(guān)系是一對(duì)多時(shí),一是使用多線程技術(shù)(異步),不管誰(shuí)啟動(dòng)線程,都可以明顯地提高系統(tǒng)性能。二是使用緩存技術(shù)(同步),但需要足夠多的資源。

  • 觀察對(duì)象可以自己做主決定是否通知觀察者,以達(dá)到減輕負(fù)擔(dān)的目的。

MVC 框架是一個(gè)典型的觀察者模式示例,Model 提供的數(shù)據(jù)是 View 的觀察對(duì)象,發(fā)布者是 Model,訂閱者是 View。Model 是指操作“不依賴于顯示形式的內(nèi)部模型”,View 是管理 Model“如何顯示”的,通常一個(gè) Model 對(duì)應(yīng)多個(gè) View。

下面將以 AM824ZB 開(kāi)發(fā)板為載體展示 MVC 框架,當(dāng)視圖觀察到模型生成的布爾類型value 值時(shí),既可以通過(guò) LED0 顯示,也可以通過(guò) zigbee 發(fā)送出去,使得可以無(wú)線遠(yuǎn)程監(jiān)控value 的值。其用例描述如下:

在初始狀態(tài)時(shí),value 為 AM_FALSE, LED 熄滅,zigbee 發(fā)送“0”。當(dāng)有鍵按下時(shí),則 value 值為 AM_TRUE,LED 點(diǎn)亮,zigbee 發(fā)送“1”;當(dāng)鍵再次按下時(shí),則 value 值為AM_FALSE,LED0 熄滅,zigbee 發(fā)送“1”......如此周而復(fù)始。

其中,value 值對(duì)應(yīng)于 Observer 模式中的模型, LED 和 zigbee 對(duì)應(yīng)于 Observer 模式中的視圖,Observer 模式描述了基本數(shù)據(jù)和它可能為數(shù)眾多的用戶界面元素之間的關(guān)系。

  • 每份數(shù)據(jù)都被封裝在一個(gè) Subject 對(duì)象中;

  • 與 Subject 對(duì)應(yīng)的每個(gè)用戶界面元素被封裝在一個(gè) Observer 對(duì)象中;

  • 一個(gè) Subject 同時(shí)可以有多個(gè) Observer;

  • 當(dāng)一個(gè) Subject 改變時(shí),會(huì)通知它所有的 Observer;

  • Observer 也會(huì)從對(duì)應(yīng)的 Subject 處獲取相應(yīng)的信息,并及時(shí)更新顯示內(nèi)容。

最終的信息存儲(chǔ)在 Subject 中,當(dāng) Subject 中的信息發(fā)生變化時(shí),Observer 會(huì)及時(shí)更新相應(yīng)的顯示內(nèi)容。當(dāng)用戶保存數(shù)據(jù)時(shí),其保存的是 Subject 中的信息,而 Observer 中的信息不需要保存,因?yàn)樗鼈冿@示的信息來(lái)自對(duì)應(yīng)的 Subject。

Observer 模式規(guī)定了單獨(dú)的 Subject 類層次和 Observer 類層次,其中的抽象基類定義了通知的協(xié)議,以及用于添加(attach)和刪除(detach)視圖的 Observer 的接口。ConcreteSubject子類實(shí)現(xiàn)特定的接口,為了讓具體的 Observer 知道什么東西發(fā)生了變化,它還需要增加相應(yīng)的接口,同時(shí) ConcreteObserver 子類通過(guò)它們的 update 操作指定如何對(duì)自己進(jìn)行更新,從而以獨(dú)一無(wú)二的方式顯示它們的 Subject。

>>> 9.3.3 領(lǐng)域模型

1. 類模型

創(chuàng)建類模型的第一步就是從問(wèn)題域?qū)ふ蚁嚓P(guān)的對(duì)象類,類常常與名詞對(duì)應(yīng),不要精挑細(xì)選,要記下所有可能的每個(gè)類。因?yàn)槲覀兊哪康氖遣东@概念,一方面并不是所有的名詞都是概念,另一方面概念也會(huì)在語(yǔ)句的其它部分中得到體現(xiàn)。比如,暫定類為 value(bool 值)、key(按鍵)、LED(發(fā)光二極管)和 zigbee。然后通過(guò)共性和差異化分析,將它們歸類到更廣泛的范疇內(nèi)。

雖然 LED 和 zigbee 屬于不同類型的對(duì)象,且它們的顯示函數(shù)也不一樣,但它們共同的概念是“視圖”和“顯示函數(shù)”。LED 具有“編號(hào)(led_id)”屬性,比如,LED0 的編號(hào)“0”用 led_id 表示,而 zigbee 具有“實(shí)例句柄(zm516x_handle)”屬性,通過(guò)實(shí)例句柄即可進(jìn)行數(shù)據(jù)的收發(fā)。因此將共同的概念用抽象類 observer_t 表示,其中的屬性通過(guò)具體類 view_led_t和 view_zigbee_t 實(shí)現(xiàn)。

雖然 value 是一個(gè) am_bool_t 值,可以將它歸類到業(yè)務(wù)邏輯,但同樣要對(duì)它建模,創(chuàng)建相應(yīng)的基類 model_t 和具體類 model_bool_t,而 value 是model_bool_t 的屬性。

下一步是尋找類間的關(guān)聯(lián),兩個(gè)或多個(gè)類之間的結(jié)構(gòu)化關(guān)系就是關(guān)聯(lián),從一個(gè)類到另一個(gè)類的引用也是關(guān)聯(lián),因此 model_t 與 observer_t 是一對(duì)多的關(guān)系。接著使用繼承共享公共結(jié)構(gòu)組織類,其相應(yīng)的類模型詳見(jiàn)圖 9.12。

圖 9.12 類模型

2. 交互模型

顯然有了類,即可創(chuàng)建模型對(duì)象model_bool 與視圖對(duì)象 view_led0 和view_zigbee。由于視圖需要知道如何調(diào)用顯示函數(shù),因此將通過(guò)在類中定義方法表示這些職責(zé)。當(dāng)模型對(duì)象的狀態(tài)變化時(shí),需要調(diào)用視圖對(duì)應(yīng)的顯示函數(shù) view_update 才能更新顯示內(nèi)容。雖然不同視圖(LED 視圖、zigbee視圖)的顯示函數(shù)的實(shí)現(xiàn)不一樣(LED 亮滅、zigbee 發(fā)送“0”或“1”),但其共性是“顯示函數(shù)”,因此可以共用 pfn_update_view 函數(shù)指針調(diào)用顯示函數(shù)。

如圖 9.13 所示的類-職責(zé)-協(xié)作序列圖展示了 model_bool、view_led0 和 view_zigbee 對(duì)象之間的消息流和由消息引起的方法調(diào)用。

圖 9.13 類-職責(zé)-協(xié)作序列圖

當(dāng)有鍵按下時(shí),即可調(diào)用 model_bool_set()修改模型對(duì)象的 value 值。當(dāng)模型對(duì)象 value值改變后,調(diào)用 model_notify()遍歷視圖對(duì)象鏈表通知所有的視圖,即調(diào)用視圖顯示函數(shù)pfn_update_view()。視圖對(duì)象在 pfn_update_view()函數(shù)的實(shí)現(xiàn)中,調(diào)用 model_bool_get()從模型對(duì)象中獲取 value 值,以便更新顯示內(nèi)容。

>>> 9.3.4 子系統(tǒng)體系結(jié)構(gòu)

集成通信圖是所有開(kāi)發(fā)用于支持用例的通信圖的合成,其形象地描述了對(duì)象之間的相互連接以及所傳遞的消息。通常不同用例之間存在執(zhí)行的優(yōu)先順序,通信圖合成的順序應(yīng)該與用例執(zhí)行的順序一致,MVC 框架的子系統(tǒng)接口圖詳見(jiàn)圖 9.14。

圖 9.14 子系統(tǒng)接口圖

  • 按鍵

當(dāng) key1 鍵按下時(shí),則布爾模型的值發(fā)生改變。首先通過(guò) model_bool_get()得到當(dāng)前的布爾值,接著將該布爾值取反,然后調(diào)用 model_bool_set()將取反后的布爾值重新設(shè)置到布爾模型中。

  • 布爾模型

布爾模型負(fù)責(zé)維護(hù)一個(gè)布爾值,外界可以通過(guò) model_bool_set()設(shè)置布爾值,也可以通過(guò) model_bool_get()獲取布爾值。當(dāng)布爾值發(fā)生改變時(shí),布爾模型將依次調(diào)用各個(gè)視圖的顯示更新函數(shù) pfn_update_view()通知各個(gè)視圖更新顯示,各視圖根據(jù)自身功能決定顯示方式。

  • 視圖

圖中包含了兩個(gè)視圖:view_led0 和 view_zigbee。

當(dāng) LED0 視圖(view_led0)接收到布爾模型發(fā)出的更新顯示通知時(shí),首先通過(guò)模型接口 model_bool_get()獲取當(dāng)前模型的布爾值。若值為 AM_TRUE,則調(diào)用 am_led_on()點(diǎn)亮LED0;若值為 AM_FALSE,則調(diào)用 am_led_off()熄滅 LED0。

當(dāng) zigbee 視圖(view_zigbee)接收到布爾模型發(fā)出的更新顯示通知時(shí),首先通過(guò)模型接口 model_bool_get()獲取當(dāng)前模型的布爾值,然后通過(guò) am_zm516x_send ()函數(shù)將布爾值通過(guò) zigbee 發(fā)送出去。

>>> 9.3.5 軟件體系結(jié)構(gòu)

1. 設(shè)計(jì)模型類圖

由于觸發(fā)事件的模型對(duì)象無(wú)法預(yù)測(cè)訂閱該事件的所有視圖對(duì)象,因此要求將視圖添加到模型的列表中保存起來(lái)。雖然可以將與模型關(guān)聯(lián)的視圖對(duì)象存放在數(shù)組中,卻不利于在運(yùn)行時(shí)動(dòng)態(tài)地添加和刪除視圖,因此選擇單向鏈表。

圖 9.15 單向鏈表示意圖

單向鏈表是由一個(gè) slist_head_t 類型的頭結(jié)點(diǎn)和若干個(gè) slist_node_t 類型的普通結(jié)點(diǎn)“鏈”起來(lái)的,其示意圖詳見(jiàn)圖 9.15,鏈表的數(shù)據(jù)結(jié)構(gòu)定義如下:

由于模型需要管理(添加、刪除和遍歷)存儲(chǔ)視圖的鏈表,因此模型需要“持有”整個(gè)視圖鏈表的頭結(jié)點(diǎn)?;诖?,需要將鏈表的 slist_head_t 類型(slist.h)頭結(jié)點(diǎn) head 包含在model_t 抽象模型類中作為數(shù)據(jù)成員:

其中,head 為鏈表的頭結(jié)點(diǎn)指針,指向存儲(chǔ)視圖的鏈表,其相應(yīng)的數(shù)據(jù)結(jié)構(gòu)示意圖詳見(jiàn)圖 9.16。由于視圖對(duì)象是存儲(chǔ)在單向鏈表中的一個(gè)結(jié)點(diǎn),因此需要將鏈表的 slist_node_t 類型(slist.h)普通結(jié)點(diǎn) node 包含在 observer_t 抽象視圖類中作為數(shù)據(jù)成員:

圖 9.16 模型數(shù)據(jù)結(jié)構(gòu)圖

當(dāng)需要增加視圖、刪除視圖或遍歷視圖時(shí),將會(huì)用到與鏈表對(duì)應(yīng)的 4 個(gè)接口函數(shù)。下面將逐一介紹,并在定義了 model_t 類型的模型對(duì)象 model 和 observer_t 類型的視圖對(duì)象observer 的前提下,展示了接口的調(diào)用形式。

  • 鏈表初始化

鏈表初始化的函數(shù)原型如下:

其調(diào)用形式如下:

在初始狀態(tài)時(shí),模型與視圖沒(méi)有任何關(guān)系,而添加和刪除視圖是調(diào)用 model_attach()和model_detach()實(shí)現(xiàn)的,這是分別調(diào)用插入鏈表結(jié)點(diǎn)函數(shù) slist_add_head()和刪除鏈表結(jié)點(diǎn)函數(shù) slist_del()實(shí)現(xiàn)的。

  • 添加視圖

添加視圖的 slist_add_head()函數(shù)原型如下:

其調(diào)用形式如下:

  • 刪除視圖

刪除視圖的 slist_del()函數(shù)原型如下:

其調(diào)用形式如下:

圖 9.17 模型內(nèi)部狀態(tài)圖

如圖 9.17 所示在模型內(nèi)部的鏈表中添加和刪除視圖的狀態(tài)圖,圖中的 attach/detach 省略了 model_固定前綴。當(dāng)調(diào)用 model_attach()時(shí),則模型關(guān)聯(lián)了一個(gè)視圖,即從初始狀態(tài)轉(zhuǎn)移到關(guān)聯(lián)一個(gè)視圖狀態(tài)。當(dāng)再次調(diào)用 model_attach()時(shí),則模型對(duì)象關(guān)聯(lián)了兩個(gè)視圖,即從關(guān)聯(lián)一個(gè)視圖狀態(tài)轉(zhuǎn)移到兩個(gè)視圖狀態(tài),以此類推。

在觀察對(duì)象的聲明周期中,如果不需要?jiǎng)h除視圖的功能,則不要實(shí)現(xiàn) model_detach()。如果不需要在運(yùn)行時(shí)動(dòng)態(tài)地添加和刪除視圖,即可在初始化時(shí)將視圖存儲(chǔ)在數(shù)組中,那么不再需要 model_attach()和 model_detach()函數(shù)。

  • 遍歷視圖

當(dāng)模型的狀態(tài)發(fā)生變化時(shí),則需要調(diào)用 model_notify()遍歷保存在模型中的視圖鏈表,并調(diào)用每個(gè)視圖的 pfn_update_view(),才能通知所有的視圖更新顯示內(nèi)容。而 model_notify()又是調(diào)用遍歷鏈表函數(shù) slist_foreach()實(shí)現(xiàn)的,遍歷視圖的 slist_foreach()函數(shù)原型如下:

其調(diào)用形式如下:

除了在序列圖中顯示了對(duì)象協(xié)作的動(dòng)態(tài)視圖外,還需要設(shè)計(jì)模型類圖表示類定義的靜態(tài)視圖描述類的屬性和方法。由于鏈表屬于基礎(chǔ)設(shè)施領(lǐng)域的概念,不是業(yè)務(wù)邏輯領(lǐng)域的概念,說(shuō)明復(fù)用級(jí)別是基于基礎(chǔ)設(shè)施域的,沒(méi)有基于核心域,因此存儲(chǔ)視圖的是數(shù)組、鏈表還是其它的容器,都不影響核心域的概念。這就是鏈表不會(huì)出現(xiàn)在分析工作流中,只有設(shè)計(jì)工作流中才考慮的原因?;诖?,不僅需要將鏈表的 slist_head_t 類型(slist.h)頭結(jié)點(diǎn) head 包含在model_t 抽象模型類中作為數(shù)據(jù)成員,而且需要將鏈表的 slist_node_t 類型(slist.h)普通結(jié)點(diǎn)node包含在observer_t抽象視圖類中作為數(shù)據(jù)成員。

圖 9.18 設(shè)計(jì)模型類圖

如圖 9.18所示為 MVC框架的設(shè)計(jì)模型類圖,圖中的“0..*”說(shuō)明模型與視圖呈現(xiàn)一對(duì)多的關(guān)系。顯然基類的方法子類也有,由于 model_attach()、model_detach()和model_notify()是在model_t類的接口中實(shí)現(xiàn)的,而在 model_bool_t 子類中沒(méi)有實(shí)現(xiàn),因此在繪制 UML 圖時(shí)則不需要重復(fù)表示,但子類還是繼承了父類的方法。而 observer_t 基類的 pfn_update_view()抽象方法是在子類中實(shí)現(xiàn)的,因此在繪制 UML 圖時(shí)必須顯式地表示。

2. 抽象視圖/ 模型

(1)抽象視圖類

當(dāng)模型對(duì)象的狀態(tài)變化時(shí),所有視圖共用函數(shù)指針 pfn_update_view,調(diào)用各自對(duì)應(yīng)的顯示函數(shù) view_update 更新顯示內(nèi)容。update_view_t 類型定義如下:

在面向?qū)ο?C++編程中時(shí),方法是通過(guò)一個(gè)隱式的 p_this 指針,使其指向函數(shù)要操作的對(duì)象,訪問(wèn)自身的數(shù)據(jù)成員。而在面向?qū)ο?C 編程時(shí),則需要顯式地聲明 p_this 指針。使其指向函數(shù)將要操作的視圖對(duì)象,訪問(wèn)視圖對(duì)象的數(shù)據(jù)成員。

p_model 指向視圖觀察的模型,其目的是獲取模型的數(shù)據(jù),使顯示數(shù)據(jù)與模型數(shù)據(jù)保持一致。由于 pfn_update_view()方法使用了模型類定義的指針變量 p_model,而在該函數(shù)的實(shí)現(xiàn)中調(diào)用了模型類的方法 model_bool_get(),即一個(gè)類使用了另一個(gè)類的操作,因此可以說(shuō)視圖依賴于模型。

依賴是兩個(gè)元素之間的一種關(guān)系,其中一個(gè)元素變化,將會(huì)導(dǎo)致另一個(gè)元素變化。雖然依賴的同義詞就是耦合和共生,但依賴是不可避免的,重要的是如何務(wù)實(shí)地應(yīng)付變化,這就是良性依賴原則。通常在 UML 中將依賴畫(huà)成一條有向的虛線,指向被依賴的類。

由此可見(jiàn),良性依賴可以幫助我們抵御 SOLID 原則與設(shè)計(jì)模式的誘惑,以免陷入過(guò)度設(shè)計(jì)的陷阱,帶來(lái)不必要的復(fù)雜性。

由于鏈表的每個(gè)結(jié)點(diǎn)存儲(chǔ)都是視圖,因此遍歷視圖鏈表需要從頭結(jié)點(diǎn) node 開(kāi)始。這是一種常見(jiàn)的 is-a 層次結(jié)構(gòu),其共同的概念用抽象類表示,差異化分析所發(fā)現(xiàn)的變化將通過(guò)從抽象類派生而來(lái)的具體類實(shí)現(xiàn)。observer_t 抽象類的定義如下:

其中,node 為鏈表結(jié)點(diǎn)成員,pfn_update_view 指向與視圖對(duì)應(yīng)的顯示函數(shù),比如,view_update,抽象類 observer_t 的類圖詳見(jiàn)圖 9.19。

圖 9.19 抽象視圖類

顯然,有了 observer_t 類,就可以定義相應(yīng)的實(shí)例 view,當(dāng)將對(duì)象 view 的地址傳給形參 p_this 后:

即可按 this 的指向引用其它成員。

雖然 pfn_update_view 看起來(lái)是一個(gè)“數(shù)據(jù)成員”,但從概念視角來(lái)看,其定義的是一個(gè)在具體視圖類中實(shí)現(xiàn)的抽象方法,其目的是將模型和視圖“解耦”。

在面向?qū)ο缶幊虝r(shí),每個(gè)對(duì)象(類)都有一個(gè)用于對(duì)象初始化的“構(gòu)造函數(shù)”,初始化成員變量等,而 C 語(yǔ)言則需要顯式地調(diào)用 view_init()初始化函數(shù)。其函數(shù)原型如下:

其中,p_this 指向視圖對(duì)象,pfn_update_view 指向與視圖對(duì)應(yīng)的顯示函數(shù),其調(diào)用形式詳見(jiàn)程序清單 9.54。

程序清單 9.54 視圖初始化函數(shù)范例程序

在 main()中調(diào)用對(duì)象 view 的初始化函數(shù) view_init(),用 OOP 術(shù)語(yǔ)來(lái)說(shuō),這是給對(duì)象 view發(fā)送一條消息,通知它進(jìn)行自我初始化。view_init()的實(shí)現(xiàn)詳見(jiàn)程序清單 9.55。

程序清單 9.55 view_init()初始化函數(shù)

在面向?qū)ο?C++編程時(shí),雖然每個(gè)類都有“構(gòu)造函數(shù)”,但有時(shí)候可能為空,因此不會(huì)將構(gòu)造函數(shù)作為方法展示在類圖中。而面向?qū)ο?C 編程——雖然 view_init()看起來(lái)像抽象視圖類提供的接口,但其功能類似于“構(gòu)造函數(shù)”,因此沒(méi)有呈現(xiàn)在相應(yīng)的類圖中。

(2)抽象模型類

由于抽象模型僅需管理與之關(guān)聯(lián)的視圖,其本質(zhì)上是管理了一個(gè)視圖鏈表,因此僅包含一個(gè)鏈表頭結(jié)。此外,還需提供增加、刪除、遍歷視圖的方法,model_t抽象類的定義如下:

其中,head 為鏈表的頭結(jié)點(diǎn),其類圖詳見(jiàn)圖 9.20。顯然有了 model_t 類,即可定義相應(yīng)的實(shí)例,當(dāng)將 model 的地址傳給形參 p_model 后:

即可按 p_model 的指向引用其它成員。

圖 9.20 抽象模型類

類似地,需要初始化模型中的各個(gè)成員,其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,其調(diào)用形式如下:

model_init()模型初始化函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.56。

程序清單 9.56 model_init()模型初始化函數(shù)

初始化后,需要將視圖保存到鏈表中。當(dāng)模型的狀態(tài)變化時(shí),即可遍歷鏈表找到與視圖對(duì)應(yīng)的顯示函數(shù),通知視圖更新顯示內(nèi)容。其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,p_observer 指向視圖對(duì)象,其調(diào)用形式詳見(jiàn)程序清單 9.57。

程序清單 9.57 添加視圖范例程序

為了避免直接訪問(wèn)數(shù)據(jù),將通過(guò)接口函數(shù)和對(duì)象交互,model_attach()添加視圖函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.58。

程序清單 9.58 model_attach()添加視圖函數(shù)

如果觀察者只對(duì)某一事件感興趣,則可以擴(kuò)展觀察對(duì)象的注冊(cè)接口,讓觀察者注冊(cè)為“僅對(duì)特定時(shí)間感興趣”,以提高更新的效率。

當(dāng)不再使用某個(gè)視圖時(shí),則將其從鏈表中刪除,其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,p_observer 指向視圖對(duì)象,其調(diào)用形式詳見(jiàn)程序清單 9.59。

程序清單 9.59 刪除視圖范例程序

model_detach()刪除視圖函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.60。

程序清單 9.60 model_detach()刪除視圖函數(shù)

當(dāng)模型對(duì)象的狀態(tài)變化時(shí),需要遍歷保存在模型中的視圖對(duì)象鏈表,并調(diào)用每個(gè)視圖對(duì)象的 pfn_update_view(),才能通知所有的視圖更新顯示內(nèi)容。其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,其調(diào)用形式詳見(jiàn)程序清單 9.61。

程序清單 9.61 遍歷視圖鏈表范例程序

model_notify()通知更新顯示內(nèi)容函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.62。

程序清單 9.62 model_notify()通知更新顯示函數(shù)

其中,slist_foreach()為遍歷視圖鏈表函數(shù),__view_process()回調(diào)函數(shù)依次處理各個(gè)鏈表結(jié)點(diǎn)(即視圖),“處理”就是調(diào)用視圖中的 pfn_update_view 函數(shù),其對(duì)應(yīng)的函數(shù)原型為:

通常在調(diào)用 pfn_update_view 函數(shù)時(shí),需要傳遞 2 個(gè)參數(shù),其分別為指向視圖的指針和指向模型的指針。

由于視圖的第一個(gè)成員為node,p_node為指向node的指針,其值為視圖首元素的地址,與視圖的地址相等,強(qiáng)制轉(zhuǎn)換即可得到指向視圖自身的指針:

此外,在 model_notify()函數(shù)調(diào)用 slist_foreach()函數(shù)時(shí),將指向模型的指針作為回調(diào)函數(shù)的參數(shù),因此__view_process()函數(shù)中的 p_arg 為指向模型的指針。為了類型匹配,強(qiáng)制轉(zhuǎn)換即可得到指向模型的指針:

此前介紹的示例只是為了展示接口的使用,實(shí)際上 model_t 和 observer_t 并沒(méi)有提供具體的實(shí)現(xiàn),需要在應(yīng)用中定義具體的視圖類和模型類,比如,針對(duì) LED 顯示可以定義一個(gè)LED 視圖類,針對(duì)布爾模型可以定義一個(gè)布爾模型類。

為了便于查閱,程序清單 9.63 展示了 mvc.h 文件的內(nèi)容。

程序清單 9.63 mvc.h 文件內(nèi)容

  • 對(duì)稱性

其實(shí)程序中處處充滿了對(duì)稱性,比如,model_attach()方法總會(huì)伴隨著 model_detach()方法,一組方法接受同樣的參數(shù),一個(gè)對(duì)象中所有的成員都具有相同的生命周期。識(shí)別出對(duì)稱性,將它清晰地表達(dá)出來(lái),使代碼更容易閱讀。一旦閱讀者理解了對(duì)稱性所涵蓋的某一半,自然也就很快地理解了另一半。

程序中的對(duì)稱性指的是概念上的對(duì)稱,無(wú)論在什么地方,同樣的概念都會(huì)以同樣的形式呈現(xiàn)。在準(zhǔn)備消滅重復(fù)之前,常常需要尋找并表示出代碼中的對(duì)稱性。

3. 具體模型/ 視圖

(1)布爾模型

雖然 model_bool_t 實(shí)現(xiàn)了 model_t 接口,但抽象模型中并沒(méi)有與應(yīng)用相關(guān)的業(yè)務(wù)邏輯,所以要在布爾模型中增加相應(yīng)的數(shù)據(jù),因?yàn)橐晥D的核心就是觀察數(shù)據(jù)并實(shí)時(shí)同步顯示。

圖 9.21 布爾模型類

雖然作為示例布爾模型僅包含一個(gè)值為 AM_TRUE 或 AM_FALSE的布爾值,但是各個(gè)視圖都可以觀察這個(gè)布爾值并實(shí)時(shí)同步顯示。其職責(zé)是管理觀察對(duì)象的狀態(tài),實(shí)現(xiàn) model_bool_get()獲取布爾值和model_bool_set()修改布爾值的方法,以及在狀態(tài)發(fā)生改變時(shí),調(diào)用基類的方法 model_notify()通知所有關(guān)聯(lián)的視圖更新顯示內(nèi)容。布爾模型的類圖詳見(jiàn)圖 9.21,其定義如下:

其中,am_bool_t 是 AMetal 在 am_types.h 中自定義的類型,其值為 AM_TRUE 或AM_FALSE。由于有了布爾模型,即可定義相應(yīng)的實(shí)例,當(dāng)將 model_bool 的地址傳給形參p_this 后:

即可按 p_this 的指向引用其它成員。

value 的初值將通過(guò)參數(shù)傳遞給初始化函數(shù),其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,init_value 為布爾模型初值。其調(diào)用形式如下:

通常應(yīng)該先初始化基類化,接著再初始化自身特有數(shù)據(jù),model_bool_init()的實(shí)現(xiàn)詳見(jiàn)程序清單 9.64。

程序清單 9.64 model_bool_init()模型初始化函數(shù)

由于布爾模型維護(hù)了一個(gè) am_bool_t 類型 value 值,因此需要提供設(shè)置和獲取 value 值的接口。model_bool_set()用于設(shè)置布爾模型當(dāng)前的布爾值,比如,當(dāng)有鍵按下時(shí),可以使用該接口修改布爾模型的值。在設(shè)置布爾模型的值時(shí),可能會(huì)使布爾模型的值發(fā)生變化。當(dāng)value 值改變時(shí),視為布爾模型的狀態(tài)發(fā)生變化,此時(shí)需要調(diào)用 model_notify()通知所有的視圖。如果布爾值未發(fā)生任何改變,則無(wú)需做任何實(shí)際動(dòng)作。其函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,value 為設(shè)置的當(dāng)前值,設(shè)置布爾模型當(dāng)前值 value 的范例程序詳見(jiàn)程序清單 9.65。

程序清單 9.65 設(shè)置布爾模型當(dāng)前值 value 的范例程序

model_bool_set()函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.66。

程序清單 9.66 model_bool_set()函數(shù)

類似地,model_bool_get()用于獲取布爾模型當(dāng)前的布爾值,比如,當(dāng)布爾模型的值發(fā)生變化時(shí),模型會(huì)通知所有的視圖更新顯示。此時(shí),在視圖顯示函數(shù)中,則需要調(diào)用該函數(shù)得到當(dāng)前最新的布爾值,同步更新顯示。獲取 value 當(dāng)前值的函數(shù)原型如下:

其中,p_this 指向模型對(duì)象,p_value 為獲取當(dāng)前值的指針,獲取布爾模型當(dāng)前值的范例程序詳見(jiàn)程序清單 9.67。

程序清單 9.67 獲取布爾模型當(dāng)前值的范例程序

model_bool_get()函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.68。

程序清單 9.68 model_bool_get()函數(shù)

為了便于查閱,程序清單 9.69 展示了 model_bool.h 文件的內(nèi)容。

程序清單 9.69 model_bool.h 文件內(nèi)容

(2)具體視圖

由于具體視圖類實(shí)現(xiàn)了 observer_t 接口,因此具體視圖還必須實(shí)現(xiàn)在抽象視圖 observer_t中定義的 update 方法,即要給抽象視圖中的 pfn_update_view 函數(shù)指針賦值,使其指向?qū)嶋H的 update 函數(shù)。

當(dāng)布爾模型的數(shù)據(jù)發(fā)生變化時(shí),視圖顯示函數(shù)需要調(diào)用 model_bool_get()獲取最新的布爾值。當(dāng)獲取布爾值后,即可根據(jù)具體視圖的實(shí)際功能同步顯示相應(yīng)的數(shù)據(jù)。

對(duì)于 LED 視圖來(lái)說(shuō),則將觀察到的 bool 值通過(guò) LED 顯示出來(lái)。即 bool 值為 AM_FALSE時(shí) LED 熄滅,bool 值為 AM_TRUE 時(shí) LED 點(diǎn)亮;對(duì)于 zigbee 視圖來(lái)說(shuō),則將觀察到的 bool值通過(guò) zigbee 發(fā)送出去,即 bool 值為 AM_FALSE 時(shí) zigbee 發(fā)送“0”,bool 值為 AM_TRUE時(shí) zigbee 發(fā)送“1”。

  • LED 視圖

LED 視圖繼承自抽象視圖,同時(shí)具有一個(gè)私有數(shù)據(jù)成員 led_id,用于表示 LED 燈的 ID號(hào),LED 視圖定義如下:

其中的 led_id 為 LED 的下標(biāo)編號(hào)。LED 視圖類 view_led_t 實(shí)現(xiàn)了 observer 接口,其對(duì)應(yīng)的類圖詳見(jiàn)圖 9.22。

圖 9.22 LED 視圖類

顯然有了 LED 視圖,即可定義相應(yīng)的視圖對(duì)象,當(dāng)將view_led的地址傳給形參p_view_led后:

即可按 p_view_led 的指向引用其它成員。

接著初始化視圖對(duì)象,顯然只要將 view_led、led_id 值傳遞給 view_led_init()函數(shù),即可初始化 LED 視圖對(duì)象,其函數(shù)原型(view_led.h)如下:

其中,p_view_led 指向 LED 視圖對(duì)象,led_id 為 LED 的編號(hào)。其調(diào)用形式如下:

通常需要先初始化抽象視圖(基類),接著再初始化私有數(shù)據(jù)成員 led_id 等,初始化抽象視圖的函數(shù)原型(mvc.h)如下:

在實(shí)現(xiàn) view_led_init()時(shí),需要先實(shí)現(xiàn)與其對(duì)應(yīng)的顯示函數(shù),詳見(jiàn)程序清單 9.70。

程序清單 9.70 LED 視圖顯示函數(shù)的實(shí)現(xiàn)

基于此,LED 視圖初始化函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.71。

程序清單 9.71 LED 視圖初始化函數(shù)的實(shí)現(xiàn)

視圖在得到通知后,需要知道究竟是 model_t 類中哪個(gè)狀態(tài)發(fā)生了變化,發(fā)生了何種變化。通常在通知接口 pfn_update_view 中不傳送這些信息,而是在視圖得到通知后,再反過(guò)來(lái)調(diào)用 model_bool_get()查詢狀態(tài)的函數(shù)。然后視圖再?zèng)Q定自己應(yīng)該做什么事情,這時(shí)pfn_update_view 的參數(shù)就是 model_t 類的指針。

當(dāng)布爾模型的狀態(tài)發(fā)生變化時(shí),為了實(shí)現(xiàn)自動(dòng)調(diào)用 LED 視圖對(duì)應(yīng)的顯示函數(shù),那么 LED視圖需要預(yù)先將自己添加到模型對(duì)象的鏈表中保存起來(lái)。比如:

為了便于查閱,程序清單 9.72 展示了 view_led.h 文件的內(nèi)容。

程序清單 9.72 view_led.h 文件內(nèi)容

至此,實(shí)現(xiàn)了一個(gè)具體模型(布爾模型)和一個(gè)具體視圖(LED 視圖),具有單個(gè)視圖的模型完整示例詳見(jiàn)程序清單 9.73。

程序清單 9.73 單個(gè)視圖的范例程序(main.c)

  • zigbee 視圖

zigbee 視圖繼承自抽象視圖,同時(shí)具有一個(gè)私有數(shù)據(jù)成員 zm516x_handle,其為 zigbee實(shí)例句柄,通過(guò)該句柄,即可使用相應(yīng)的接口函數(shù)操作 zigbee 模塊,zigbee 視圖定義如下:

zigbee 視圖類 view_zigbee_t 實(shí)現(xiàn)了 observer_t 接口,其對(duì)應(yīng)的類圖詳見(jiàn)圖 9.23。

圖 9.23 zigbee 視圖類

有了zigbee 視圖,即可定義相應(yīng)的視圖對(duì)象,當(dāng)將 view_zigbee 的地址傳給 p_view_zigbee 后:

即可按 p_view_zigbee 的指向引用其它成員。

類似地,定義 zigbee 視圖的初始化函數(shù)原型如下:

其中,p_view_zigbee 指向 zigbee 視圖對(duì)象,zm516x_handle 是 zigbee 模塊的實(shí)例句柄,可通過(guò) ZM516X 模塊的實(shí)例初始化函數(shù)獲得。其調(diào)用形式如下:

在實(shí)現(xiàn) view_zigbee_init()時(shí),也需要先實(shí)現(xiàn)與其對(duì)應(yīng)的顯示函數(shù),詳見(jiàn)程序清單 9.74。

程序清單 9.74 zigbee 視圖顯示函數(shù)的實(shí)現(xiàn)

其中,am_zm516x_send()函數(shù)的作用是通過(guò) zigbee 發(fā)送字符串至目標(biāo)地址的節(jié)點(diǎn)(目標(biāo)地址在初始化時(shí)配置),其函數(shù)原型(am_zm516x.h)為:

基于此,zigbee 視圖初始化函數(shù)的實(shí)現(xiàn)詳見(jiàn)程序清單 9.75,其除了初始化抽象視圖類外,還完成了 zigbee 模塊的地址配置,配置本地地址為 0x2001,目標(biāo)地址為 0x2002。

程序清單 9.75 zigbee 視圖初始化函數(shù)的實(shí)現(xiàn)

為了便于查閱,程序清單 9.76 展示了 view_zigbee.h 文件的內(nèi)容。

程序清單 9.76 view_zigbee.h 文件內(nèi)容

由此可見(jiàn),當(dāng)新增加 zigbee 視圖后,雖然與 LED 視圖不一樣,但可以共用同一個(gè)模型。

且 LED 視圖和布爾模型都不需要做任何修改,同時(shí)也沒(méi)有一行重復(fù)的視圖代碼,說(shuō)明“用戶界面與內(nèi)部實(shí)現(xiàn)”真正做到了分離。

3. MVC 應(yīng)用

在 MVC 模式中,其核心是視圖接收來(lái)自模型和控制器的數(shù)據(jù)并決定如何顯示,控制器捕捉用戶的輸入事件和系統(tǒng)產(chǎn)生的事件。當(dāng)控制器檢測(cè)到有鍵按下時(shí),它將外部的事件轉(zhuǎn)換為內(nèi)部的數(shù)據(jù)請(qǐng)求,控制器決定調(diào)用模型的那個(gè)函數(shù)進(jìn)行處理,然后確定用那個(gè)視圖來(lái)顯示模型提供的數(shù)據(jù),詳見(jiàn)程序清單 9.77。

程序清單 9.77 MVC 模式應(yīng)用范例程序(main.c)

至此,實(shí)現(xiàn)了具有 LED 和 zigbee 兩個(gè)視圖的 MVC 應(yīng)用程序,為了驗(yàn)證 zigbee 視圖,實(shí)現(xiàn)遠(yuǎn)程“監(jiān)控”,需要使用另外一個(gè) zigbee 來(lái)接收 MVC 應(yīng)用中 zigbee 視圖發(fā)出的數(shù)據(jù)“0”或“1”。為便于觀察,使用另外一塊 AM824ZB 開(kāi)發(fā)板來(lái)接收數(shù)據(jù),當(dāng)接收到“0”時(shí),其LED0 熄滅,當(dāng)接收到“1”時(shí),其 LED0 點(diǎn)亮,范例程序詳見(jiàn)程序清單 9.78。

程序清單 9.78 新增 AM84ZB 板用以接收 zigbee 數(shù)據(jù)的范例程序

在 MVC 應(yīng)用中,zigbee 視圖將 zigbee 的本地地址設(shè)置為 0x2001,目標(biāo)地址設(shè)置為0x2002,。新的 AM824ZB 開(kāi)發(fā)板為了能夠接收到其發(fā)出的數(shù)據(jù),需要對(duì)應(yīng)的將本地地址設(shè)置為 0x2002,目標(biāo)地址設(shè)置為 0x2001。

實(shí)際上,MVC 模式常用于處理 GUI 窗口事件,比如,每個(gè)窗口部件都是 GUI 相關(guān)事件的發(fā)布者,其它對(duì)象可以訂閱所關(guān)注的事件。比如,當(dāng)按下 A 按鈕時(shí),會(huì)發(fā)布相應(yīng)的“動(dòng)作事件”。另一個(gè)對(duì)象對(duì)這個(gè)按鈕進(jìn)行注冊(cè),便于在此按鈕按下時(shí),得到相應(yīng)的消息,然后完成某一動(dòng)作。

由此可見(jiàn),觀察者模式背后的思想等同于關(guān)注點(diǎn)分離原則背后的思想,其目的是降低發(fā)布者和訂閱者之間的耦合,便于在運(yùn)行時(shí)動(dòng)態(tài)地添加和刪除訂閱者。模式在抽象的原則和具體的實(shí)踐之間架起了一座橋梁,其主要?jiǎng)訖C(jī)是將變化帶來(lái)的影響局部化。

局部化影響的必然結(jié)果就是“捆綁邏輯和數(shù)據(jù)”,如果有可能盡量將其放在一個(gè)方法中,至少要放在一個(gè)對(duì)象里,最起碼也要放到一個(gè)包下面。在發(fā)生變化時(shí),邏輯和數(shù)據(jù)很可能會(huì)同時(shí)被改動(dòng)。如果將它們放在一起,那么修改它們所造成的的影響停留在局部。

其次,觀察者模式的最大推動(dòng)力來(lái)自于 OCP 開(kāi)放閉合原則,其動(dòng)機(jī)就是為了在增加新的觀察者對(duì)象時(shí),無(wú)需更改觀察對(duì)象,從而使觀察對(duì)象保持封閉,這對(duì)于系統(tǒng)的擴(kuò)展性和靈活性有很大的提高。顯然由繼承實(shí)現(xiàn)的 OCP,使設(shè)計(jì)模式成為應(yīng)變能力更強(qiáng)的工具。

>>> 9.3.6 MVC 應(yīng)用程序優(yōu)化

在整個(gè)布爾模型的應(yīng)用中,使用的硬件外設(shè)資源有 1 個(gè)按鍵、1 個(gè) LED 和 zigbee 模塊,這些資源都有相應(yīng)的可以跨平臺(tái)的通用接口。雖然程序清單 9.77 中絕大部分程序都沒(méi)有與硬件綁定,可以跨平臺(tái)復(fù)用,但是唯一的不足之處在于在應(yīng)用程序中調(diào)用了實(shí)例初始化函數(shù)am_zm516x_inst_init(),而實(shí)例初始化函數(shù)是與平臺(tái)相關(guān)的,不同平臺(tái)可能不同,因此若實(shí)例初始化函數(shù)修改,則應(yīng)用程序必須進(jìn)行對(duì)應(yīng)的修改。

顯然,實(shí)例初始化函數(shù)是初始化具體實(shí)例的,而應(yīng)用程序并不關(guān)心具體實(shí)例,其只需要使用具體實(shí)例提供的通用服務(wù)(如 LED、zigbee、KEY)。基于此,將實(shí)例初始化函數(shù)的調(diào)用從應(yīng)用程序中“分離”出去,應(yīng)用程序全部使用通用接口實(shí)現(xiàn)。使用 LED,需要 LED 對(duì)應(yīng)的 ID 號(hào),使用按鍵,需要按鍵對(duì)應(yīng)的編碼,使用 zigbee,需要 zigbee 的操作句柄,這些信息都可以通過(guò)參數(shù)傳遞。優(yōu)化后的應(yīng)用程序范例詳見(jiàn)程序清單 9.79。

程序清單 9.79 應(yīng)用程序?qū)崿F(xiàn)(app_mvc_bool_main.c)

顯然,只需要準(zhǔn)備好 1 個(gè) LED、1 個(gè)按鍵和一個(gè) zigbee 資源(調(diào)用它們對(duì)應(yīng)的實(shí)例初始化函數(shù)),然后調(diào)用 app_mvc_bool_main()函數(shù)即可,為了便于調(diào)用 app_mvc_bool_main()函數(shù),將該函數(shù)聲明在 app_mvc_bool_main.h 中,詳見(jiàn)程序清單 9.80。

程序清單 9.80 應(yīng)用程序入口函數(shù)聲明(app_mvc_bool_main.h)

在主程序中調(diào)用 app_mvc_bool_main()函數(shù)即可啟動(dòng)應(yīng)用,范例程序詳見(jiàn)程序清單 9.81。

程序清單 9.81 啟動(dòng)應(yīng)用程序(main.c)

注意,AM824ZB 板載的獨(dú)立按鍵 KEY1 和 LED 均在系統(tǒng)啟動(dòng)時(shí)自動(dòng)調(diào)用了實(shí)例初始化函數(shù),因此,無(wú)需再次調(diào)用。默認(rèn)情況下,LED0 的 ID 為 0,KEY1 的按鍵編碼為 KEY_F1。

此時(shí),若應(yīng)用程序需要移動(dòng)到其它硬件平臺(tái)上運(yùn)行,或相關(guān)資源的 ID 發(fā)生變化,則只需要完善“啟動(dòng)應(yīng)用程序”這一部分代碼即可,其往往就是根據(jù)實(shí)際情況調(diào)用各個(gè)硬件實(shí)例的初始化函數(shù)。將資源的“準(zhǔn)備”工作(初始化)從原先的應(yīng)用程序中分離出來(lái),使得應(yīng)用程序徹底的通用化了,與具體硬件實(shí)現(xiàn)了完全的分離,可以靈活的跨平臺(tái)應(yīng)用。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • ZigBee
    +關(guān)注

    關(guān)注

    158

    文章

    2262

    瀏覽量

    242103
  • 可編程邏輯
    +關(guān)注

    關(guān)注

    7

    文章

    514

    瀏覽量

    44009
  • 無(wú)線模塊
    +關(guān)注

    關(guān)注

    12

    文章

    617

    瀏覽量

    48290

原文標(biāo)題:周立功:MVC 框架的應(yīng)用

文章出處:【微信號(hào):ZLG_zhiyuan,微信公眾號(hào):ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    鴻蒙OS開(kāi)發(fā):典型頁(yè)面場(chǎng)景【一次開(kāi)發(fā),多端部署】實(shí)戰(zhàn)(設(shè)置典型頁(yè)面)

    示例展示了設(shè)置應(yīng)用的典型頁(yè)面,其在小窗口和大窗口有不同的顯示效果,體現(xiàn)一次開(kāi)發(fā)、多端部署的能力。
    的頭像 發(fā)表于 05-27 09:36 ?897次閱讀
    鴻蒙OS開(kāi)發(fā):<b class='flag-5'>典型</b>頁(yè)面場(chǎng)景【一次開(kāi)發(fā),多端部署】實(shí)戰(zhàn)(設(shè)置<b class='flag-5'>典型</b>頁(yè)面)

    AMD Versal? Adaptive SoC CPM PCIE PIO EP設(shè)計(jì)CED示例

    本文可讓開(kāi)發(fā)者們看懂 AMD Vivado Design Tool 2023.2 中的“AMD Versal Adaptive SoC CPM PCIE PIO EP 設(shè)計(jì)”CED 示例。?
    的頭像 發(fā)表于 05-10 09:39 ?378次閱讀
    AMD Versal? Adaptive <b class='flag-5'>SoC</b> CPM PCIE PIO EP設(shè)計(jì)CED<b class='flag-5'>示例</b>

    為什么很少用C++開(kāi)發(fā)單片機(jī)

    C語(yǔ)言是面向過(guò)程的語(yǔ)言,C++是面向對(duì)象編程語(yǔ)言。結(jié)合本文來(lái)說(shuō),面向過(guò)程相比面向
    發(fā)表于 03-25 14:26 ?654次閱讀
    為什么很少用C++開(kāi)發(fā)單片機(jī)

    plc梯形圖編程的基本原則

    PLC(可編程邏輯控制器)是現(xiàn)代自動(dòng)化控制領(lǐng)域中廣泛應(yīng)用的一種控制裝置。PLC的梯形圖編程是PLC控制中最常用的編程方式之一。本文將詳細(xì)介紹PLC梯形圖編程的基本
    的頭像 發(fā)表于 01-22 10:51 ?1499次閱讀

    YOLOv8實(shí)現(xiàn)旋轉(zhuǎn)對(duì)象檢測(cè)

    YOLOv8框架在在支持分類、對(duì)象檢測(cè)、實(shí)例分割、姿態(tài)評(píng)估的基礎(chǔ)上更近一步,現(xiàn)已經(jīng)支持旋轉(zhuǎn)對(duì)象檢測(cè)(OBB),基于DOTA數(shù)據(jù)集,支持航拍圖像的15個(gè)類別對(duì)象檢測(cè),包括車輛、船只、典型
    的頭像 發(fā)表于 01-11 10:43 ?1372次閱讀
    YOLOv8實(shí)現(xiàn)旋轉(zhuǎn)<b class='flag-5'>對(duì)象</b>檢測(cè)

    基于C/C++面向對(duì)象的方式封裝socket通信類流程簡(jiǎn)析

    在掌握了基于 TCP 的套接字通信流程之后,為了方便使用,提高編碼效率,可以對(duì)通信操作進(jìn)行封裝,本著有淺入深的原則,先基于 C 語(yǔ)言進(jìn)行面向過(guò)程的函數(shù)封裝,然后再基于 C++ 進(jìn)行面向對(duì)象
    的頭像 發(fā)表于 12-26 10:00 ?1457次閱讀

    基于C/C++面向對(duì)象的方式封裝socket通信類

    在掌握了基于 TCP 的套接字通信流程之后,為了方便使用,提高編碼效率,可以對(duì)通信操作進(jìn)行封裝,本著有淺入深的原則,先基于 C 語(yǔ)言進(jìn)行面向過(guò)程的函數(shù)封裝,然后再基于 C++ 進(jìn)行面向對(duì)象
    的頭像 發(fā)表于 12-26 09:57 ?1140次閱讀

    程序員的10條基本編程原則

    編寫(xiě)代碼容易,但編寫(xiě)優(yōu)秀代碼卻是一項(xiàng)挑戰(zhàn)。采納基本編程原則是確保編寫(xiě)高質(zhì)量代碼的穩(wěn)妥途徑,無(wú)論軟件項(xiàng)目規(guī)模大小,都能保證代碼高效、易讀、可靠、安全且易維護(hù)。
    的頭像 發(fā)表于 12-05 11:28 ?763次閱讀
    程序員的10條基本<b class='flag-5'>編程</b><b class='flag-5'>原則</b>

    淺談C語(yǔ)言面向對(duì)象編程思想

    C語(yǔ)言是一種面向過(guò)程的語(yǔ)言,但是也可以用結(jié)構(gòu)體和函數(shù)指針來(lái)模擬面向對(duì)象的特性,比如封裝、繼承和多態(tài)。
    發(fā)表于 11-02 12:27 ?795次閱讀

    非常實(shí)用,推薦一種面向對(duì)象思維的單片機(jī)程序框架

    非常實(shí)用,推薦一種面向對(duì)象思維的單片機(jī)程序框架
    的頭像 發(fā)表于 10-24 18:03 ?433次閱讀
    非常實(shí)用,推薦一種<b class='flag-5'>面向</b><b class='flag-5'>對(duì)象</b>思維的單片機(jī)程序框架

    MCU上面可以實(shí)現(xiàn)面向對(duì)象編程嗎?

    為什么都是用的面向過(guò)程編程
    發(fā)表于 10-24 07:39

    基于面向對(duì)象的熱流體系統(tǒng)建模方法及應(yīng)用研究

    以多領(lǐng)域統(tǒng)一面向對(duì)象建模語(yǔ)言 Modelica 為例,詳細(xì)分析了面向對(duì)象建模仿真的原理及技術(shù)特征,結(jié)合熱流體系統(tǒng)本身的特點(diǎn),探索了基于面向
    發(fā)表于 10-11 15:29 ?0次下載

    Java中的Reactive編程示例

    相信響應(yīng)式編程經(jīng)常會(huì)在各種地方被提到。本篇就為大家從函數(shù)式編程一直到Spring WeFlux做一次簡(jiǎn)單的講解,并給出一些示例,希望大家可以更好的理解響應(yīng)式編程,可以在合適的時(shí)機(jī)運(yùn)用到
    的頭像 發(fā)表于 10-08 16:06 ?836次閱讀
    Java中的Reactive<b class='flag-5'>編程</b><b class='flag-5'>示例</b>

    面向對(duì)象思想封裝IIC、AT24C64驅(qū)動(dòng)

    使用面向對(duì)象編程思想封裝IIC驅(qū)動(dòng),將IIC的屬性和操作封裝成一個(gè)庫(kù),在需要?jiǎng)?chuàng)建一個(gè)IIC設(shè)備時(shí)只需要實(shí)例化一個(gè)IIC對(duì)象即可,本文是基于STM32和HAL庫(kù)做進(jìn)一步封裝的。
    的頭像 發(fā)表于 10-08 15:35 ?759次閱讀

    Python編程實(shí)用指南

    Python 是一種解釋型、面向對(duì)象、動(dòng)態(tài)數(shù)據(jù)類型的高級(jí)程序設(shè)計(jì)語(yǔ)言。通過(guò) Python 編程,我們能夠解決現(xiàn)實(shí)生活中的很多任務(wù)。本書(shū)是一本面向實(shí)踐的 Python
    發(fā)表于 09-27 06:21