設(shè)計(jì)模式在UI系統(tǒng)開發(fā)中的應(yīng)用(導(dǎo)讀)

整理硬盤,發(fā)現(xiàn)了2012年撰寫的內(nèi)部培訓(xùn)PPT教程。
當(dāng)時(shí)主要從事UI方面的工作,并且負(fù)責(zé)公司內(nèi)部培訓(xùn),因此為了培訓(xùn)相關(guān)開發(fā)人員,特意編寫了一個(gè)基于windows 的C++ UI庫(當(dāng)時(shí)移動(dòng)尚未如此之火熱,雖然是win32,但是思想是一樣的,可以用于任何系統(tǒng)),用于演示UI的核心概念以及面向?qū)ο?/strong>和設(shè)計(jì)模式相關(guān)的內(nèi)容。

面向?qū)ο蠛驮O(shè)計(jì)模式的作用是什么?

我個(gè)人認(rèn)為就是: 通過面向?qū)ο螅O(shè)計(jì)模式等思想或手段形成一種機(jī)制:封裝不變的部分,將可變的部分以虛函數(shù)或事件回調(diào)等方式公開給調(diào)用方。

面向?qū)ο蠼坛谭譃?面向?qū)ο笮枨蠓治觯?uml基礎(chǔ)及用法,實(shí)現(xiàn)一個(gè)復(fù)雜控件三部分組成(建立在設(shè)計(jì)模式實(shí)現(xiàn)的類庫上,等設(shè)計(jì)模式好了后,再開源)

設(shè)計(jì)模式教程由六個(gè)部分程序組成

設(shè)計(jì)模式代碼庫.png

UIPLib.lib(核心庫)

UI的核心是什么?

我個(gè)人將其歸納為: "一個(gè)中心,四個(gè)基本點(diǎn)"

一個(gè)中心: 以樹數(shù)據(jù)結(jié)構(gòu)(控件樹)為中心

四個(gè)基本點(diǎn): 控件的布局、控件的事件分發(fā)及處理,控件的臟區(qū)局部刷新、控件的渲染

控件的布局/事件/刷新/渲染都是建立在控件樹不同的遍歷基礎(chǔ)上的。

由此可知,UIPLib的功能就是實(shí)現(xiàn)上述的“一個(gè)中心,四個(gè)基本點(diǎn)框架“功能。

之所以說是框架,是因?yàn)樗⒘艘惶讬C(jī)制,但是實(shí)際使用需要從該機(jī)制對(duì)應(yīng)的各個(gè)基類進(jìn)行繼承實(shí)現(xiàn)。具體我們會(huì)在后面看到。

將不變的部分封裝在類庫中,將可變部分聲明為虛方法,由繼承者來進(jìn)行override,從而達(dá)到天人合一之境。

內(nèi)容2.png
 1. 容器用到的設(shè)計(jì)模式:適配器/迭代器/策略/裝飾
  • ArrayList.h 中實(shí)現(xiàn)了泛型的動(dòng)態(tài)數(shù)組,類似std::vector或js中的Array對(duì)象

  • Iterator.h 中主要定義了迭代器模式和線性列表的遍歷方向策略(從左到右,從右到左)

迭代器.png
線性遍歷策略.png
  • Node.h 中定義了基類CNode以及CTypeNode 泛型類,所有子類,請(qǐng)繼承自CTypeNode類

  • NodeIterator.h中定義了樹節(jié)點(diǎn)線性迭代需要的相關(guān)內(nèi)容

樹節(jié)點(diǎn)遍歷.png
遍歷之間的關(guān)系1.png
遍歷之間的關(guān)系2.png
遍歷之間的關(guān)系3.png

之所以花這么多力氣來解釋樹的遍歷,是因?yàn)槠渲匾裕瑯浣Y(jié)構(gòu)可以說是我用的最多,最強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)。一定要掌握他!

UIPText.exe程序中,進(jìn)行了8個(gè)迭代器的測試:

nodeIteratorTest.png
迭代測試結(jié)果.png
2.  ControlSystem用到的設(shè)計(jì)模式:組合及模板方法
  • 在這個(gè)部分中,使用了結(jié)構(gòu)型設(shè)計(jì)模式-組合(其實(shí)樹結(jié)構(gòu)就是經(jīng)典的組合模式)以及模板方法的模式。

  • 通過ControlSystem系統(tǒng),建立一個(gè)自定義控件的插件體系
    后面看到的UIPControl.lib就是具體控件的實(shí)現(xiàn),而UIPLib中的ControlSystem定義的是共性部分。典型的插件方式。

  • 通過ControlSystem提供CControlBase基類,你可以繼承CControlBase并實(shí)現(xiàn)自己的控件,然后可以將一個(gè)控件組成一個(gè)插件lib,或者一系列控件組成一個(gè)插件lib,進(jìn)行鏈接導(dǎo)入,靈活性大增,后面會(huì)了解如何使用的。

  • ControlManager是Control的根節(jié)點(diǎn),也是控件布局/事件分發(fā)/局部刷新/渲染的起始節(jié)點(diǎn)!!

    3. 事件系統(tǒng)用到的設(shè)計(jì)模式: 觀察者
    
  • 使用開源的FastDelegate庫,并在FastDelegate基礎(chǔ)上增加了多播功能(實(shí)際就是實(shí)現(xiàn)了C#的delegate功能),并支持觸發(fā)順序的調(diào)整(其實(shí)該實(shí)現(xiàn)源碼來自于Torque3D Engine)

  • 和C# 事件類似的系統(tǒng),對(duì)于自定義事件,只需如下:

自定義Render事件.png

定義好三者,注冊(cè)事件處理函數(shù),可以在任意地方觸發(fā)事件,在任意地方,以全局函數(shù),成員方法或靜態(tài)方法等方式進(jìn)行回調(diào),超級(jí)好用的類C#事件系統(tǒng)

  • 這里用到的觀察者模式和書上的經(jīng)典描述有差別,因此在UIPTest.exe中實(shí)現(xiàn)了一個(gè)經(jīng)典版的觀察者模式,可以調(diào)用看看結(jié)果。并且在教程中分析了Java awt中的事件監(jiān)聽方式。

  • 對(duì)C# delegate事件體系的擴(kuò)展:Document Object Model (DOM) Level 3 Events Specification

關(guān)于該事件系統(tǒng)(也就是經(jīng)典的冒泡事件系統(tǒng)),我是非常喜歡的。因此在后來,我花了點(diǎn)時(shí)間,研究webkit相關(guān)源碼,從中剝離出干凈代碼移植到的代碼中。

其實(shí)DOM Event事件系統(tǒng)依賴如下幾個(gè)特點(diǎn):

  1. 需要一個(gè)樹結(jié)構(gòu)----已經(jīng)具有控件樹了
  2. 需要事件監(jiān)聽優(yōu)先級(jí)----在FastDelegate基礎(chǔ)上實(shí)現(xiàn)了根據(jù)權(quán)重調(diào)整觸發(fā)的先后順序
  3. DOM Event事件支持多播觸發(fā)----在FastDelegate基礎(chǔ)上實(shí)現(xiàn)了多播功能
  4. 冒泡與和捕抓----需要自己實(shí)現(xiàn),我們只要實(shí)現(xiàn)這個(gè)就可以了
    其實(shí)冒泡事件的使用是很有技巧的,用的好的話,是很令人愉悅的體驗(yàn)。

關(guān)于冒泡事件,并沒有在本系列教程中,如有需要就自己擴(kuò)展一下

4. 渲染系統(tǒng)用到的設(shè)計(jì)模式:工廠方法
渲染系統(tǒng).png
  • 面向接口編程,使用工廠方法返回接口
  • 實(shí)現(xiàn)了GDI 渲染器,很容易擴(kuò)展到其他渲染backend.
  • 將結(jié)果渲染到IRenderImage中,然后bitblt到顯存,通過這種方式(雙緩存)避免閃爍
  • 使用渲染到紋理(IRenderImage),有利于應(yīng)用于3DAPI中,例如opengl/directX。
5.臟區(qū)局部刷新用到的設(shè)計(jì)模式: 模板方法
  • 因?yàn)槭褂脀indows,所以使用了windows自有的InvalidateRect API進(jìn)行臟區(qū)標(biāo)記,用于觸發(fā)重繪
  • 如果使用各自系統(tǒng)的控件體系,基本都有自己的臟區(qū)標(biāo)記函數(shù),例如android中的invalidate方法,ios中的setNeedsDisplayInRect方法,這些API都可以指定一個(gè)rect,需要重繪的地方限制在該rect中。
  • 關(guān)于臟區(qū)的使用:
臟區(qū)檢查.png
  • CControlBase的Render方法規(guī)定了調(diào)用的流程:
render模板方法.png
  • 由此可見,子類override OnXXXRender時(shí),必須要先調(diào)用基類相同名稱的方法才OK。
    例如UIPControl.lib中自定義的控件的OnRender方法的override必須要要調(diào)用基類同名方法
例子.png
  • 關(guān)于臟區(qū)重繪,是個(gè)很好玩的領(lǐng)域,是2D游戲或UI引擎性能提高的關(guān)鍵要素。在這個(gè)部分,我有過一定的研究,并且在opengl/directX上實(shí)現(xiàn)了一個(gè)效率極高的臟區(qū)刷新及渲染引擎。實(shí)際上,gl/dx這種圖形api,通過修改投影矩陣,我們可以在頂點(diǎn)處理階段就進(jìn)行裁剪,將渲染效率大幅度提高。、這部分涉及到gl/dx的渲染流水線以及數(shù)學(xué)相關(guān)知識(shí)。在完成本文檔后再深入的了解一些比較好玩的內(nèi)容。

  • 關(guān)于控件的布局,包括控件的尺寸和位置計(jì)算,是最復(fù)雜的一個(gè)部分,在我們現(xiàn)在的引擎中,并沒有實(shí)現(xiàn)具體的布局引擎,關(guān)于布局,在完成本文檔后再深入的了解布局相關(guān)內(nèi)容。 說實(shí)話,布局算法太多了,這個(gè)需要深入思考后,具體一一描述了。每個(gè)算法背后都是有優(yōu)缺點(diǎn)。

    7.  UIPLib的入口類CApplication用到的設(shè)計(jì)模式:單例和模板方法
    
  • init模板方法,初始化各個(gè)子系統(tǒng)

初始化模板方法.png
  • 鼠標(biāo)和鍵盤事件的分發(fā)

  • RunLoop

  • CApplication本身是單例模式,支持游戲模式(獨(dú)占模式,cpu 100%)和UI模式

UIPControl.lib庫(實(shí)現(xiàn)兩個(gè)在后面程序中要用到的控件)

內(nèi)容3.png
  • UIPIconButton 用來顯示帶Icon的Button,演示了如何增加事件以及渲染

  • UIPaintArea 與Canvas類似,規(guī)定了一個(gè)顯示區(qū),并演示了CRenderEvent事件如何使用

paintArea.png

UIPAnimation.exe (以UIPLib/UIPControl為基礎(chǔ),實(shí)現(xiàn)一個(gè)貝塞爾路徑跟隨動(dòng)畫演示效果)

生成路徑.gif
貝塞爾曲線路徑跟隨及方向調(diào)整.gif

用到的設(shè)計(jì)模式:

  • ISprite使用橋接模式實(shí)現(xiàn)
    //動(dòng)畫精靈接口,橋接模式
    //橋接模式的核心是接口與實(shí)現(xiàn)分離
    //也就是面向接口編程

  • 工廠方法(使用std::shared_ptr防止內(nèi)存泄露)

精靈工廠.png
  • CPathFollower類使用flyWeight模式
flyweight.png
  • CAnimationController使用備忘錄模式,用于實(shí)現(xiàn)Record/Replay功能

UIPPaint.exe (以UIPLib/UIPControl為基礎(chǔ),實(shí)現(xiàn)一個(gè)簡單的繪圖程序)

UIPaint.png
paint.png
  • CDrawerVisitor類使用了Vistor模式,用于渲染輔助功能
  • 使用Command模式實(shí)現(xiàn)了Undo/Redo功能,在程序中你可以使用鼠標(biāo)中鍵來進(jìn)行Undo,使用鼠標(biāo)右鍵來進(jìn)行Redo.或者使用ctrl+z進(jìn)行Undo,ctrl+y進(jìn)行Redo.
  • ResponsibleChain系統(tǒng)使用職責(zé)鏈模式實(shí)現(xiàn)了一個(gè)幫助系統(tǒng),程序中你按F1,會(huì)根據(jù)當(dāng)前的控件焦點(diǎn)顯示幫助內(nèi)容
  • 程序中各個(gè)圖標(biāo)的切換隨之功能切換(例如按圓形就繪制圓圈),使用狀態(tài)機(jī)模式來管理,整個(gè)操作就非常清晰明了,特別棒
  • CFacadeMediator類是整個(gè)核心結(jié)構(gòu),即實(shí)現(xiàn)了門面模式,又實(shí)現(xiàn)了中間者模式,他是大內(nèi)總管,所有的事情都需要到這個(gè)類來中轉(zhuǎn)。
狀態(tài)子系統(tǒng).png
圖元子系統(tǒng).png
undo_redo子系統(tǒng).png
事件.png
其他.png
事件處理.png

具體細(xì)節(jié)和源碼,這幾天會(huì)全部出來,畢竟都是現(xiàn)成的PPT,我就偷懶直接截圖黏貼好了。分如下三篇:

CoreLib篇

Control/Animation篇

Paint篇

2017/8/15日更新說明:

關(guān)于此系列 原本4篇發(fā)布的續(xù)集目前撤回關(guān)閉。

因?yàn)?

  • 原本是win32 gdi實(shí)現(xiàn),目前改成c++ opengles實(shí)現(xiàn)(使用nanovg庫),跨平臺(tái),可以運(yùn)行在ios/android/linux/windows上
  • js作為上層語言進(jìn)行調(diào)用
  • 目前實(shí)現(xiàn)的是C# delegate類似事件回調(diào),在此基礎(chǔ)上再實(shí)現(xiàn)如下兩個(gè)事件系統(tǒng):
    冒泡事件系統(tǒng)Document Object Model (DOM) Level 3 Events Specification【已實(shí)現(xiàn)】
    職責(zé)鏈?zhǔn)录到y(tǒng)(ios UIResponser)

關(guān)于三大js引擎(ms chakra core/google v8/mozilla spidemonkey 技術(shù)選型過程)以及跨平臺(tái)渲染引擎(cairo/google skia/nanovg技術(shù)選型過程)記錄在如下幾篇周記中(還記錄了其他一些東西)。目前理論基礎(chǔ)已經(jīng)可行性驗(yàn)證都已通過!

周記(2017/3/26-2017/4/2)

開發(fā)周記(2017/4/3-2017/4/9)

開發(fā)周記(2017/4/10-2017/4/16)

開發(fā)周記(2017/4/24-2017/5/1)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容