青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

在寫DirectUI時有這么一個需求,就是加載一串XML,如何生成一棵對應的控件樹?

比如XML如下:

<panel>

       <button id=”1” text=”button”/>

<label id=”2” text=”label”/>

      <panel>

            <label id=”3” text=”good”/>

        </panel>

 

</panel>

如何生成對應的Control Tree?

 

看到這個,我們首先想到的是先對這個控件層次體系進行設計, 并抽象出如下層次:

Class CControlBase

{};

 

Class CButton: public CControlBase

{};

 

Class CLabel: public CControlBase

{};

 

Class CContainer: ControlBase

{};

 

Class CPanel: public CContainer

{};

 

我們可以看到他們的控件類層次如下:

以上層次符合一般面向對象設計的規則,其中CContainer表示控件容器類的基類。

那么如何根據XML創建這些對象呢?

我們想到了抽象工廠(Abstract Factory), 定義如下:

Class CControlFactory

{

Public:

       CButton* CreateButton() { return new CButton;}

       CLabel* CreateLabel() { return new CLabel;}

       CPanel* CreatePanel() { return new CPanel;}

}

 

顯然,以上設計每次加入一個新控件都要重新加一個Create方法,不符合面向對象設計的開放封閉原則(OCP,Open Closed Principle), 因此我們把它改成下面的接口:

Class CControlFactory

{

Public:

       CControlBase* CreateControl(const string& strTypeName)

{

              If(strTypeName==”button”)

return new Button;

              Else if(strTypeName==”label”)

return new Label;

              Else if(strTypeName==”panel”)

return new Panel;

              Else

                     Return null;

}

};

 

顯然上面的設計盡管比前一個好多了, 但是仍然有硬編碼(hard code)的味道, 每次新加一個控件,都要在這里改代碼。

 

如何才能新加控件,又不影響這里的代碼呢?

我們想到了注冊機制, 設計如下:

Class CControlFactory

{

Public:

       Bool RegisterControl(const string& strTypeName, void* lpfnCreateFun);

       CControlBase* CreateControl(const string& strTypeName) ;

 

Protected:

       map<string, void*> m_controlMap;

};

我們看到我們上面的設計是通過注冊控件和它的創建方法,保存在一個Map里,然后在CreateControl時通過查詢這個Map,調用控件注冊的創建方法, 來生成新控件。

可以看到上面的設計已經完全符合開放封閉原則,我們新加控件完全不會影響現有的代碼。

 

但是考慮這樣一個需求,要求新生成控件要有默認風格,并且這些風格是可變的,比如我們要生成的Button 默認風格是要求以某個圖片為背景。

 

顯然上面注冊創建函數的方法滿足不了我們這里的需求,因為我們不可能通過在我們的創建函數里硬編碼來指定初始控件風格。

 

怎么樣才能讓我們新創建的控件有默認風格,并且該默認風格是可變的?

我們想到了設計模式里創建型模式中的原型(Prototype)模式, 給我們的基本控件增加一個Clone方法, 代碼如下:

Class CControlBase

{

Public:

       Virtual CControlBase* Clone() { return new CControlBase(*this);}

};

 

Class CButton: public CControlBase

{

Public:

       Virtual CControlBase* Clone() { return new CButton (*this);}

};

 

Class CLabel: public CControlBase

{

Public:

       Virtual CControlBase* Clone() { return new CLabel (*this);}

};

 

Class CContainer: ControlBase

{

Public:

       Virtual CControlBase* Clone() { return new CContainer (*this);}

};

 

Class CPanel: public CContainer

{

Public:

       Virtual CControlBase* Clone() { return new CPanel (*this);}

};

 

而我們ControlFactory的設計如下:

Class CControlFactory

{

Public:

       Bool RegisterControl(const string& strTypeName, CControlBase* pPrototypeControl);

       CControlBase* CreateControl(const string& strTypeName) ;

 

Protected:

       map<string, CControlBase*> m_controlMap;

};

可以看到在注冊時我們把控件原型保存起來,然后在CreateControl時通過該控件原型的Clone方法,創建我們的新控件。這種設計下,我們只要在注冊時提供不同的原型控件,后面創建時就有不同的默認風格,非常方便。

 

至此,我們基本完成了ControlFactory的工作。

這里還可以優化的是這里的ControlFactory我們可以把它定義成單例(Singleton:

Class CControlFactory

{

Public:

       Static CControlFactory* GetInstacne();

Public:

       Bool RegisterControl(const string& strTypeName, CControlBase* pPrototypeControl);

       CControlBase* CreateControl(const string& strTypeName) ;

      

Protected:

       map<string, CControlBase*> m_controlMap;

};

 

有了Control層次和Control Factory,我們接下來考慮如何解析XML來生成Control Tree?

一般的設計會寫一個如下的類:

Class CControlBuilder

{

       Public:

              CControlBase* BuildControlTree(const string& strXML);

};

上面的設計直接通過傳入XML,生成Control Tree,看起來很完美。

 

但是顯然這里有2件事情要做,一件是XML的解析,另一件是Control Tree的生成。我們把這2個東西柔在一個類里面,對我們以后維護很不方便。

 

比如說我們本來用TinyXML來解析XML的,但是后來發現它是基于DOM的,效率太低,想改用別的XML解析器,這時你就會發現修改CControlBuilder這個類是多么痛苦了。

 

顯然,我們比較好的設計是我們應該把XML解析和Build Control Tree2個功能分離開來,這也符合面向對象設計時的單一職責原則(Single Responsibility Principle)。

 

我們抽象出一個XML的解析接口:

 

Class IXMLParser

{

       Public:

              Virtual bool SetDoc(const cstring& strXML);

              Virtual string GetTagName();

       Virtual bool FindElem(const string& strName );

       Virtual bool FindChildElem(const string& strName );

       Virtual bool IntoElem();

       Virtual bool OutOfElem();

       Virtual string GetAttrib(const string& strName) const;

       Virtual string GetChildAttrib(const string& strName) const;

};

 

Class CTinyXMLParser: public IXMLParser

{

      

};

 

可以看到通過這種方式,我們把XML的解析過程抽象出來,我們本身不用關心解析器的類型(說明:上面XML解析的設計不一定合理,只是一個示例)。

 

現在我們可以開始寫我們的Control Tree Builder了,

Class CControlBuilder:

{

       Public:

              CControlBase* BuildControlTree(const string& strXML)

              {

                     CTinyXMLParser parser;

                     parser.SetDoc(strXML);

                     CControlFactory* pFactory = CControlFactory::Instance();

                    

                     String strControl = parser.GetTagName();

                     CControlBase* pRoot = pFactory->CreateContorl(strControl);

                     ….

 

                     Return pRoot;

}

};

 

顯然上面的方式, 我們修改XML解析器的類型很不方便,我們可以通過工廠方法(Factory Method)來方便以后擴展。

 

 

Class CControlBuilder

{

       Public:

              CControlBase* BuildControlTree(const string& strXML)

              {

                     Auto_ptr< IXMLParser > parser = CreateXMLParser() ;

                     Parser->SetDoc(strXML);

                     CControlFactory* pFactory = CControlFactory::Instance();

                    

                     String strControl = parser->GetTagName();

                     CControlBase* pRoot = pFactory->CreateContorl(strControl);

                     ….

 

                     Return pRoot;

}

 

Protected:

Virtual IXMLParser* CreateXMLParser()

{

       Return new CTinyXMLParser;

}

};

 

好了,到這里我們所有的設計都全部完成了,基本類圖如下:

PS, 在設計CControlBuilder時,本來考慮是不是該用設計模式中的生成器(builder)模式, 但是Builder模式強調的是同樣的創建過程,生成不同的產品。顯然我們這里無論XML如何解析,最終只有Root Control這一種產品。 所以這里如果用這個模式的話,就有點過度設計了。

 

總結一下,我們上面用了哪些設計模式?
Composite, Abstract Factory, Factory Method, Singleton, Prototype.
你看出來了嗎?
你們有更好的設計思路嗎?

 說明: (1)上面的代碼都只是設計時的偽代碼,拿來編譯肯定過不了
        (2)考慮Windows的窗口類注冊和創建機制,會發現盡管WindowsAPIC語言方式,但是設計思想是類似的。
        (3) 對于DirectUI來說,上面控件類的這種層次設計其實很濫, 有興趣的話可以學下WPFJ

 

 

posted on 2012-06-10 17:31 Richard Wei 閱讀(4597) 評論(4)  編輯 收藏 引用 所屬分類: 設計模式

FeedBack:
# re: 生成DirectUI 控件樹的設計過程
2012-06-11 16:38 | 飯中淹
我覺得有幾個問題
1- UI依賴了XML。因為你如果要改變數據源,你需要改變UI內部的東西。
我做的系統里,用了抽象的DOM數據NODE的概念。XML只是建立數據NODE的一個源而已。

2- UI的創建很多都用不到。比如prototype這種,如果你想讓你的UI從數據上創建,那么你就用一個從數據創建就好了。

我的系統里,UI控件的CREATE都是只有統一的一個參數,IDataNode。這樣接口非常簡單。

  回復  更多評論
  
# re: 生成DirectUI 控件樹的設計過程
2012-06-11 18:12 | Richard Wei
@飯中淹

沒太看明白你說的問題, 但是我想對于UI來說很多東西是類似的,
就一個UI(窗口)本身是一棵遞歸的樹,內部有容器和控件,相當于樹的分支(node)和樹葉(leaf),并且可以無限遞歸, 這個層次很符合Composite模式,也很符合用XML來描述。

所以現在比較流行的UI框架(比如WPF, WEB頁面)都分2層來描述,一層是標記(markup). 比如WPF里的XAML, Web頁面里的HTML; 另外一層是代碼(code), 比如WPF里用C#, Web頁面里用javascript.

其實本質上code部分才是真正的實現,內部定義了所有的控件層次, 所以即使不依賴markup部分,我們同樣可以定義和操作所有的UI, 但是借助Markup可以讓我們更方便的生成和修改UI。

至于用不用Prototype模式不太重要,我這里的想法是讓控件有默認的風格屬性,而且這個默認風格屬性也是容易修改的。通過用Prototype,我可以在XML里定義默認的控件原型,程序初始化時用該原型來注冊控件就可以了。另外, 如果有些控件你覺得用不到,不去注冊就好了。 這種注冊機制, 對于支持插件(控件)也很方便,你覺得呢?  回復  更多評論
  
# re: 生成DirectUI 控件樹的設計過程
2012-06-12 14:09 | 飯中淹
。。。。并不是說用XML不好,或者否定UI樹什么的。

我也是用的MARKUP來建立UI控件樹,并綁定代碼。不過,我是用了一個中間的DOM抽象數據層。
這樣,不管外面有什么XML,HTML甚至INI,我只要一個數據抽象層放進UI里面就好了。





@Richard Wei
  回復  更多評論
  
# re: 生成DirectUI 控件樹的設計過程
2012-06-26 15:46 | 叫我老王吧
最近也在寫UI,受益匪淺,受教了  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            99视频一区| 久久久久久久国产| 国产精品日韩欧美一区| 欧美一区2区三区4区公司二百| 亚洲第一在线视频| 欧美一区二区三区精品电影| 亚洲人体一区| 久久精品1区| 久久爱91午夜羞羞| 欧美一级久久| 久久久精品日韩| 欧美亚洲日本网站| 亚洲成人中文| 亚洲成在线观看| 欧美日韩精品综合| 欧美日韩在线免费| 欧美日韩激情小视频| 国产精品sm| 狠狠做深爱婷婷久久综合一区| 国产午夜精品久久久久久久| 国际精品欧美精品| 亚洲国产精品va在线看黑人| 日韩一级成人av| 久久国产精品久久久久久久久久 | 久久精视频免费在线久久完整在线看 | 久久久999精品免费| 理论片一区二区在线| 欧美日韩免费看| 亚洲国产精品久久人人爱蜜臀 | 欧美专区在线| 欧美日韩成人一区二区三区| 国产一区二区三区最好精华液| 久久琪琪电影院| 国产精品扒开腿做爽爽爽软件| 国产一区二区三区日韩欧美| 亚洲免费播放| 老巨人导航500精品| 性欧美超级视频| 国产精品久久久久久久电影| 亚洲国产一二三| 欧美激情二区三区| 美女诱惑黄网站一区| 国产综合久久久久久鬼色| 中文久久乱码一区二区| 老司机精品视频网站| 99视频精品免费观看| 中文国产成人精品| 亚洲靠逼com| 欧美日韩国产精品成人| 亚洲精品久久久久久下一站 | 麻豆精品传媒视频| 亚洲精品免费电影| 99国产精品99久久久久久| 欧美亚洲成人精品| 久久精品国内一区二区三区| 久久国产直播| 一本色道久久综合狠狠躁篇的优点| 亚洲黄色三级| 国产精品激情| 免费欧美日韩| 国产伦精品一区二区三区| 久久国产精品久久久久久| 一区二区三区福利| 亚洲毛片一区二区| 国内自拍一区| 亚洲视频999| 亚洲欧洲一区二区在线播放| 亚洲网站在线| 亚洲老司机av| 久久av一区| 欧美亚洲日本一区| 欧美日韩一区二区在线| 免费在线观看日韩欧美| 国产色视频一区| 亚洲最新合集| 在线亚洲高清视频| 欧美激情1区2区3区| 久久一日本道色综合久久| 国产精品成人va在线观看| 亚洲日本中文字幕免费在线不卡| 国产一区二区三区在线免费观看| 一本色道久久综合| 亚洲欧美日本伦理| 国产精品一卡二| 欧美在线国产精品| 免费一级欧美片在线观看| 狠狠久久亚洲欧美专区| 久久久精品国产99久久精品芒果| 另类人畜视频在线| 亚洲午夜黄色| 黄色成人在线免费| 蜜桃av一区二区三区| 亚洲欧洲在线播放| 一本色道久久综合亚洲精品高清 | 韩国女主播一区| 亚洲影音先锋| 国产自产2019最新不卡| 久久亚洲国产成人| 一本色道久久综合亚洲精品按摩 | 亚洲专区国产精品| 久久激情久久| 夜夜精品视频一区二区| 国产精品网站在线观看| 久久九九国产精品怡红院| 亚洲品质自拍| 欧美成黄导航| 午夜一区二区三区不卡视频| 国产亚洲网站| 国产精品久久久久久久一区探花| 久久黄金**| 午夜免费久久久久| 亚洲午夜一区| 一区二区三区www| 亚洲精品在线三区| 蜜臀久久久99精品久久久久久| 性伦欧美刺激片在线观看| 在线不卡亚洲| 国产婷婷97碰碰久久人人蜜臀| 欧美日韩精品一区二区在线播放| 亚洲欧美在线另类| 亚洲欧美日韩国产中文| 欧美亚洲综合久久| 午夜欧美大尺度福利影院在线看| 亚洲精品国产精品国产自| 亚洲福利视频网站| 亚洲美女尤物影院| 在线视频欧美日韩精品| 亚洲天堂成人在线观看| 亚洲一级黄色| 久久精品中文字幕一区二区三区 | 亚洲电影专区| 国产乱码精品一区二区三区不卡| 久久一区视频| 欧美日韩精品一区二区在线播放 | 久久欧美肥婆一二区| 久久久久9999亚洲精品| 久久一二三四| 一区二区三区精品久久久| 午夜精品视频在线| 欧美freesex8一10精品| 国产精品一区免费视频| 亚洲第一区中文99精品| 亚洲女人小视频在线观看| 久久亚洲影院| 亚洲桃花岛网站| 欧美另类在线观看| 亚洲福利视频一区| 久久成人这里只有精品| 亚洲国产乱码最新视频| 午夜精品一区二区三区电影天堂 | 国产日产精品一区二区三区四区的观看方式 | 性欧美精品高清| 欧美日韩精品伦理作品在线免费观看 | 日韩一级在线| 久久综合狠狠综合久久综合88| 一本色道久久综合亚洲精品婷婷 | 99re热精品| 麻豆成人综合网| 久久九九免费视频| 国产亚洲精品福利| 在线午夜精品| 免费看黄裸体一级大秀欧美| 欧美一区二区三区久久精品茉莉花| 欧美日本免费一区二区三区| 亚洲日本视频| 日韩视频精品在线| 国产精品ⅴa在线观看h| 亚洲一级电影| 久久亚洲午夜电影| 在线亚洲电影| 国产视频精品xxxx| 欧美在线播放视频| 久久精品视频在线看| 最新亚洲视频| 亚洲少妇在线| 亚洲国产精品悠悠久久琪琪| 91久久黄色| 国产精品久久久久毛片大屁完整版| 亚洲欧美一区二区三区极速播放| 亚洲一本视频| 一区二区三区日韩欧美| 欧美一级欧美一级在线播放| 激情一区二区| 宅男噜噜噜66一区二区| 在线成人av网站| 午夜精品999| 羞羞色国产精品| 欧美日本中文| 亚洲成色最大综合在线| 加勒比av一区二区| 正在播放亚洲一区| 一区二区不卡在线视频 午夜欧美不卡在| 一区二区三区国产| 一本色道久久综合狠狠躁的推荐| 久久国产精品亚洲77777| 亚洲综合视频一区| 国产精品日韩久久久久| 一个色综合导航| 亚洲视频日本| 国产精品jvid在线观看蜜臀|