• <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>

            在寫DirectUI時(shí)有這么一個(gè)需求,就是加載一串XML,如何生成一棵對(duì)應(yīng)的控件樹?

            比如XML如下:

            <panel>

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

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

                  <panel>

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

                    </panel>

             

            </panel>

            如何生成對(duì)應(yīng)的Control Tree?

             

            看到這個(gè),我們首先想到的是先對(duì)這個(gè)控件層次體系進(jìn)行設(shè)計(jì), 并抽象出如下層次:

            Class CControlBase

            {};

             

            Class CButton: public CControlBase

            {};

             

            Class CLabel: public CControlBase

            {};

             

            Class CContainer: ControlBase

            {};

             

            Class CPanel: public CContainer

            {};

             

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

            以上層次符合一般面向?qū)ο笤O(shè)計(jì)的規(guī)則,其中CContainer表示控件容器類的基類。

            那么如何根據(jù)XML創(chuàng)建這些對(duì)象呢?

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

            Class CControlFactory

            {

            Public:

                   CButton* CreateButton() { return new CButton;}

                   CLabel* CreateLabel() { return new CLabel;}

                   CPanel* CreatePanel() { return new CPanel;}

            }

             

            顯然,以上設(shè)計(jì)每次加入一個(gè)新控件都要重新加一個(gè)Create方法,不符合面向?qū)ο笤O(shè)計(jì)的開(kāi)放封閉原則(OCPOpen 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;

            }

            };

             

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

             

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

            我們想到了注冊(cè)機(jī)制, 設(shè)計(jì)如下:

            Class CControlFactory

            {

            Public:

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

                   CControlBase* CreateControl(const string& strTypeName) ;

             

            Protected:

                   map<string, void*> m_controlMap;

            };

            我們看到我們上面的設(shè)計(jì)是通過(guò)注冊(cè)控件和它的創(chuàng)建方法,保存在一個(gè)Map里,然后在CreateControl時(shí)通過(guò)查詢這個(gè)Map,調(diào)用控件注冊(cè)的創(chuàng)建方法, 來(lái)生成新控件。

            可以看到上面的設(shè)計(jì)已經(jīng)完全符合開(kāi)放封閉原則,我們新加控件完全不會(huì)影響現(xiàn)有的代碼。

             

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

             

            顯然上面注冊(cè)創(chuàng)建函數(shù)的方法滿足不了我們這里的需求,因?yàn)槲覀儾豢赡芡ㄟ^(guò)在我們的創(chuàng)建函數(shù)里硬編碼來(lái)指定初始控件風(fēng)格。

             

            怎么樣才能讓我們新創(chuàng)建的控件有默認(rèn)風(fēng)格,并且該默認(rèn)風(fēng)格是可變的?

            我們想到了設(shè)計(jì)模式里創(chuàng)建型模式中的原型(Prototype)模式, 給我們的基本控件增加一個(gè)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的設(shè)計(jì)如下:

            Class CControlFactory

            {

            Public:

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

                   CControlBase* CreateControl(const string& strTypeName) ;

             

            Protected:

                   map<string, CControlBase*> m_controlMap;

            };

            可以看到在注冊(cè)時(shí)我們把控件原型保存起來(lái),然后在CreateControl時(shí)通過(guò)該控件原型的Clone方法,創(chuàng)建我們的新控件。這種設(shè)計(jì)下,我們只要在注冊(cè)時(shí)提供不同的原型控件,后面創(chuàng)建時(shí)就有不同的默認(rèn)風(fēng)格,非常方便。

             

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

            這里還可以優(yōu)化的是這里的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,我們接下來(lái)考慮如何解析XML來(lái)生成Control Tree?

            一般的設(shè)計(jì)會(huì)寫一個(gè)如下的類:

            Class CControlBuilder

            {

                   Public:

                          CControlBase* BuildControlTree(const string& strXML);

            };

            上面的設(shè)計(jì)直接通過(guò)傳入XML,生成Control Tree,看起來(lái)很完美。

             

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

             

            比如說(shuō)我們本來(lái)用TinyXML來(lái)解析XML的,但是后來(lái)發(fā)現(xiàn)它是基于DOM的,效率太低,想改用別的XML解析器,這時(shí)你就會(huì)發(fā)現(xiàn)修改CControlBuilder這個(gè)類是多么痛苦了。

             

            顯然,我們比較好的設(shè)計(jì)是我們應(yīng)該把XML解析和Build Control Tree2個(gè)功能分離開(kāi)來(lái),這也符合面向?qū)ο笤O(shè)計(jì)時(shí)的單一職責(zé)原則(Single Responsibility Principle)

             

            我們抽象出一個(gè)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

            {

                  

            };

             

            可以看到通過(guò)這種方式,我們把XML的解析過(guò)程抽象出來(lái),我們本身不用關(guān)心解析器的類型(說(shuō)明:上面XML解析的設(shè)計(jì)不一定合理,只是一個(gè)示例)

             

            現(xiàn)在我們可以開(kāi)始寫我們的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解析器的類型很不方便,我們可以通過(guò)工廠方法(Factory Method)來(lái)方便以后擴(kuò)展。

             

             

            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;

            }

            };

             

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

            PS, 在設(shè)計(jì)CControlBuilder時(shí),本來(lái)考慮是不是該用設(shè)計(jì)模式中的生成器(builder)模式, 但是Builder模式強(qiáng)調(diào)的是同樣的創(chuàng)建過(guò)程,生成不同的產(chǎn)品。顯然我們這里無(wú)論XML如何解析,最終只有Root Control這一種產(chǎn)品。 所以這里如果用這個(gè)模式的話,就有點(diǎn)過(guò)度設(shè)計(jì)了。

             

            總結(jié)一下,我們上面用了哪些設(shè)計(jì)模式?
            Composite, Abstract Factory, Factory Method, Singleton, Prototype.
            你看出來(lái)了嗎?
            你們有更好的設(shè)計(jì)思路嗎?

             說(shuō)明: (1)上面的代碼都只是設(shè)計(jì)時(shí)的偽代碼,拿來(lái)編譯肯定過(guò)不了
                    (2)考慮Windows的窗口類注冊(cè)和創(chuàng)建機(jī)制,會(huì)發(fā)現(xiàn)盡管WindowsAPIC語(yǔ)言方式,但是設(shè)計(jì)思想是類似的。
                    (3) 對(duì)于DirectUI來(lái)說(shuō),上面控件類的這種層次設(shè)計(jì)其實(shí)很濫, 有興趣的話可以學(xué)下WPFJ

             

             

            posted on 2012-06-10 17:31 Richard Wei 閱讀(4543) 評(píng)論(4)  編輯 收藏 引用 所屬分類: 設(shè)計(jì)模式

            FeedBack:
            # re: 生成DirectUI 控件樹的設(shè)計(jì)過(guò)程
            2012-06-11 16:38 | 飯中淹
            我覺(jué)得有幾個(gè)問(wèn)題
            1- UI依賴了XML。因?yàn)槟闳绻淖償?shù)據(jù)源,你需要改變UI內(nèi)部的東西。
            我做的系統(tǒng)里,用了抽象的DOM數(shù)據(jù)NODE的概念。XML只是建立數(shù)據(jù)NODE的一個(gè)源而已。

            2- UI的創(chuàng)建很多都用不到。比如prototype這種,如果你想讓你的UI從數(shù)據(jù)上創(chuàng)建,那么你就用一個(gè)從數(shù)據(jù)創(chuàng)建就好了。

            我的系統(tǒng)里,UI控件的CREATE都是只有統(tǒng)一的一個(gè)參數(shù),IDataNode。這樣接口非常簡(jiǎn)單。

              回復(fù)  更多評(píng)論
              
            # re: 生成DirectUI 控件樹的設(shè)計(jì)過(guò)程
            2012-06-11 18:12 | Richard Wei
            @飯中淹

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

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

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

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

            我也是用的MARKUP來(lái)建立UI控件樹,并綁定代碼。不過(guò),我是用了一個(gè)中間的DOM抽象數(shù)據(jù)層。
            這樣,不管外面有什么XML,HTML甚至INI,我只要一個(gè)數(shù)據(jù)抽象層放進(jìn)UI里面就好了。





            @Richard Wei
              回復(fù)  更多評(píng)論
              
            # re: 生成DirectUI 控件樹的設(shè)計(jì)過(guò)程
            2012-06-26 15:46 | 叫我老王吧
            最近也在寫UI,受益匪淺,受教了  回復(fù)  更多評(píng)論
              
            久久ww精品w免费人成| 久久精品国产99国产精偷| 伊人色综合久久天天| 99精品久久精品| 久久夜色精品国产噜噜噜亚洲AV | 久久99精品国产麻豆宅宅| 久久久WWW成人免费精品| 久久青青草原国产精品免费 | 99久久这里只有精品| 亚洲精品无码久久久久去q| 久久人妻少妇嫩草AV蜜桃| 亚洲欧洲中文日韩久久AV乱码| 精品久久国产一区二区三区香蕉| 成人精品一区二区久久久| 精品久久久久国产免费| 久久99热这里只有精品国产 | 久久综合亚洲色一区二区三区| 亚洲国产成人久久精品99| 女人高潮久久久叫人喷水| 一本一道久久a久久精品综合| 久久精品桃花综合| 久久综合国产乱子伦精品免费| avtt天堂网久久精品| 中文字幕一区二区三区久久网站 | 亚洲国产成人精品女人久久久 | 亚洲午夜久久久久妓女影院| 欧美一区二区三区久久综| 国产亚洲精品自在久久| 国产成人综合久久精品尤物| 久久夜色精品国产www| 久久精品国产亚洲AV忘忧草18| 狠狠88综合久久久久综合网 | 久久久久久av无码免费看大片| 久久精品国产欧美日韩99热| 色婷婷综合久久久中文字幕| 精品久久久久久国产91| 无码国内精品久久人妻麻豆按摩| 亚洲人成网亚洲欧洲无码久久 | 久久本道久久综合伊人| 久久久久久亚洲精品影院| 久久久av波多野一区二区|