以上層次符合一般面向?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)放封閉原則(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;
}
};
顯然上面的設(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 Tree這2個(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)盡管Windows的API是C語(yǔ)言方式,但是設(shè)計(jì)思想是類似的。
(3) 對(duì)于DirectUI來(lái)說(shuō),上面控件類的這種層次設(shè)計(jì)其實(shí)很濫, 有興趣的話可以學(xué)下WPFJ