以上層次符合一般面向對象設計的規則,其中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 Tree這2個功能分離開來,這也符合面向對象設計時的單一職責原則(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的窗口類注冊和創建機制,會發現盡管Windows的API是C語言方式,但是設計思想是類似的。
(3) 對于DirectUI來說,上面控件類的這種層次設計其實很濫, 有興趣的話可以學下WPFJ