一、“對象創建”模式
該類型模式的定義為:
通過“對象創建”模式繞開直接new一個具體的類型,來避免對象創建(new)過程中所導致的緊耦合(依賴具體類型),從而支持對象創建的穩定。它是接口抽象之后的第一步工作。
其典型模式為:
Factory Method(工廠方法)
Abstract Factory(抽象工廠)
Prototype(原型模式)
Builder(構建器)
(1)工廠方法
<1>該方法的動機為:
在軟件系統中,經常會面臨著創建對象的工作,由于需求變化,需要創建的對象的具體類型也會經常的變化。
<2>該方法的定義為:
定義一個用于創建對象的接口,讓子類決定實例化哪個類。Factory Method 使得一個類的實例化延遲(目的:解耦,手段:虛函數)到子類。
在講述該設計模式時,采用文件分割器的例子。
為了能夠實現不同文件分割器的設計,就需要創建不同類型的對象,若使用new方法,則需要針對不同的文件分割器new不同的對象類型。
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
// 具體實現類
class BinarySplitter : public ISplitter{};
class TxtSplitter: public ISplitter{};
class PictureSplitter: public ISplitter{};
class VideoSplitter: public ISplitter{};
在new時就依賴了一個具體的實現類 ISplitter* splitter = new BinarySplitter(),這樣就違反了依賴倒置原則。
為了能夠避開new方法依賴于具體類這一個問題,使用工廠模式,形成一個新的工廠基類,并將其寫成一個虛函數。每一種實現方法都有一個自己的工廠類,這個工廠類都繼承于工廠基類。
//抽象類
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
//工廠基類
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0; // 虛函數
virtual ~SplitterFactory(){}
};
//具體類
class BinarySplitter : public ISplitter{};
class TxtSplitter: public ISplitter{};
class PictureSplitter: public ISplitter{};
class VideoSplitter: public ISplitter{};
//具體工廠
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};
class TxtSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
};
class PictureSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter();
}
};
class VideoSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter();
}
}
通過上述方法,在MainForm中沒有依賴于具體類,而是依賴于抽象類。
class MainForm : public Form
{
SplitterFactory* factory;//工廠
public:
MainForm(SplitterFactory* factory){
this->factory=factory;
}
void Button1_Click(){
ISplitter * splitter=factory->CreateSplitter(); //多態new
splitter->split();
}
};
要點總結:
<1>Factory Method 模式用于隔離類對象的使用者和具體類型之間的耦合關系。面對一個經常變化的具體類型,緊耦合關系(new)會導致軟件的脆弱。
<2>Factory Method模式通過面向對象的手法,將所要創建的對象工作延遲到子類,從而實現一種擴展(而非更改)的策略,較好地解決了這種緊耦合關系。
<3>Factory Method模式解決“單個對象”的需求變化。缺點在于要求創建方法/參數相同。
(2)抽象工廠
<1>該方法的動機:
在軟件系統中,經常會面臨著“一系列相互依賴的對象”的創建工作,同時由于需求的變化,往往存在著更多系列對象的創建工作。
<2>該方法的定義:
提供一個接口,讓該接口負責創建一系列“性關系或相互依賴的對象”,無需指定他們的具體類。
以下代碼是對數據庫的操作:
class EmployeeDAO{
public:
vector<EmployeeDO> GetEmployees(){
SqlConnection* connection = new SqlConnection();
connection->ConnectionString = "...";
SqlCommand* command = new SqlCommand();
command->CommandText="...";
command->SetConnection(connection);
SqlDataReader* reader = command->ExecuteReader();
while (reader->Read()){
}
}
};
上述代碼適用于SQL Server數據庫,但是不能實現對Oracle、MySql等數據庫的兼容。
為此可以設計一個數據庫訪問有關操作的基類,后續不同數據庫的操作都繼承與這個基類。同時為各個操作創建其相對應的工廠類。
//數據庫訪問有關的基類
class IDBConnection{
};
class IDBConnectionFactory{
// 工廠類
public:
virtual IDBConnection* CreateDBConnection()=0;
};
class IDBCommand{
};
class IDBCommandFactory{
// 工廠類
public:
virtual IDBCommand* CreateDBCommand()=0;
};
class IDataReader{
};
class IDataReaderFactory{
// 工廠類
public:
virtual IDataReader* CreateDataReader()=0;
};
//支持SQL Server
class SqlConnection: public IDBConnection{
};
class SqlConnectionFactory:public IDBConnectionFactory{
};
class SqlCommand: public IDBCommand{
};
class SqlCommandFactory:public IDBCommandFactory{
};
class SqlDataReader: public IDataReader{
};
class SqlDataReaderFactory:public IDataReaderFactory{
};
//支持Oracle
class OracleConnection: public IDBConnection{
};
class OracleCommand: public IDBCommand{
};
class OracleDataReader: public IDataReader{
};
class EmployeeDAO{
IDBConnectionFactory* dbConnectionFactory;
IDBCommandFactory* dbCommandFactory;
IDataReaderFactory* dataReaderFactory;
public:
vector<EmployeeDO> GetEmployees(){
IDBConnection* connection = dbConnectionFactory->CreateDBConnection();
connection->ConnectionString("...");
IDBCommand* command = dbCommandFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //關聯性
IDBDataReader* reader = command->ExecuteReader(); //關聯性
while (reader->Read()){
}
}
};
由于上述各個操作是彼此關聯的,所以可以講上述一系列操作類對象放在同一個工廠類中。
在一定程度上,工廠方法可以看做時抽象工廠的一種特例。
//數據庫訪問有關的基類
class IDBConnection{
};
class IDBCommand{
};
class IDataReader{
};
class IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
//支持SQL Server
class SqlConnection: public IDBConnection{
};
class SqlCommand: public IDBCommand{
};
class SqlDataReader: public IDataReader{
};
class SqlDBFactory:public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
//支持Oracle
class OracleConnection: public IDBConnection{
};
class OracleCommand: public IDBCommand{
};
class OracleDataReader: public IDataReader{
};
class EmployeeDAO{
IDBFactory* dbFactory;
public:
vector<EmployeeDO> GetEmployees(){
IDBConnection* connection = dbFactory->CreateDBConnection();
connection->ConnectionString("...");
IDBCommand* command = dbFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //關聯性
IDBDataReader* reader = command->ExecuteReader(); //關聯性
while (reader->Read()){
}
}
};
要點總結:
<1>如果沒有對應“多系列對象構建”的需求變化,則沒有必要使用Abstract Factory 模式,這時候使用簡單的工廠完全可以。
<2>“系列對象”指的是在某一特定系列下的對象之間有相互依賴或作用關系。不同系列的對象之間不能相互依賴。
<3>Abstract Factory 模式主要在于應對“新系列”的需求變動,其缺點在于難以應對“新對象”的需求變動。
(3)原型模式
<1>該模式的動機:
在軟件系統中,經常面臨著“某些結構復雜的對象”的創建工作;由于需求的變化,這些對象經常面臨著劇烈的變化,但是他們卻擁有著比較穩定一直的接口。
<2>該模式的定義:
使用原型實例指定創建對象的種類,然后通過拷貝這些原型來創建新的對象。
現在將工廠模式所舉的例子中的ISplitter和SplitterFactor兩個基類進行合并,同時添加一個克隆自己的方式來構建對象的虛方法(注意為深度拷貝,使用類的拷貝構造函數)。
//抽象類
class ISplitter{
public:
virtual void split()=0;
virtual ISplitter* clone()=0; //通過克隆自己來創建對象
virtual ~ISplitter(){}
};
//具體類
class BinarySplitter : public ISplitter{
public:
virtual ISplitter* clone(){
return new BinarySplitter(*this);
}
};
class TxtSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new TxtSplitter(*this);
}
};
class PictureSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new PictureSplitter(*this);
}
};
class VideoSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new VideoSplitter(*this);
}
};
class MainForm : public Form
{
ISplitter* prototype;//原型對象
public:
MainForm(ISplitter* prototype){
this->prototype=prototype;
}
void Button1_Click(){
ISplitter * splitter=
prototype->clone(); //克隆原型
splitter->split();
}
};
如此克隆的目的是什么?
對于某些結構較為復雜的對象,當利用工廠模式進行創建時會非常復雜。有時我們需要的是具備某些“屬性”的對象,這也是工廠模式無法做到的,為此需要進行克隆。
要點總結:
<1>Prototype模式同樣用于隔離類對象的使用者和具體類型(易變類)直接的耦合關系,他同樣要求這些“易變類”擁有“穩定的接口”。
<2>Prototype模式對于“如何創建易變類的實體對象”采用“原型克隆”的方法來做,它使得我們可以非常靈活地“動態創建”擁有某些穩定接口的新對象——所需工作僅僅是注冊一個新類的對象(即原型),然后在任何需要的地方clone。
<3>Prototype模式中的Clone方法可以利用某些框架中的序列化來實現深拷貝。(C++無需利用序列化進行深拷貝)
(4)構建器
<1>該模式的動機:
在軟件系統中,有時候面臨著“一個復雜對象”的創建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將他們組合在一起的算法卻相對穩定。
<2>該模式的定義:
將一個復雜對象的構建與其表示相分離,使得同樣的構建過程(穩定)可以創建不同的表示(變化)。
該類型方法與Template Method模板方法相似,但其主要是為了解決對象的創建問題。
在該類型方法的講解中使用了房屋建造的例子,房屋的建造過程是相對固定的,但是具體的墻、門、窗可能都存在差異。
在代碼中將固定的部分實現為虛函數的流程。具體的對象則繼承House類,并重寫虛函數。
class House{
//....
};
class HouseBuilder {
public:
House* GetResult(){
return pHouse;
}
virtual ~HouseBuilder(){}
protected:
House* pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
class HouseDirector{
public:
HouseBuilder* pHouseBuilder;
HouseDirector(HouseBuilder* pHouseBuilder){
this->pHouseBuilder=pHouseBuilder;
}
House* Construct(){
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag=pHouseBuilder->BuildPart3();
if(flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
};
二、接口隔離模式
在組建構建過程中,某些接口之間直接的依賴常常會帶來很多問題、甚至根本無法實現。采用添加一層間接接口(穩定的),來隔離本來相互緊密關聯的接口是一種常見的解決方案。
其典型模式為:
Facade(門面模式)
Proxy(代理模式
Adapter(適配器)
Mediator(中介者)
(1)門面模式
對于客戶系統和子系統之前存在很多的耦合的情況,如果不考慮設計的情況,那么會形成A方案的情況,系統的依賴嚴重,維護性大大降低。
如果在客戶層和子系統之間添加一層Facade,那么客戶系統之和Facade打交道,子系統中也只和Facade打交道,那么在這時候,也就減少了客戶和自系統的依賴程度,相對使兩個系統更加獨立,可維護提高。
<1>門面模式的動機:
上述方案A的問題在于組件的客戶和組件中的各種復雜的子系統有了過多的耦合,隨著外部客戶程序和子系統的演化,這種過多的耦合面臨很多變化的挑戰。
<2>門面模式的定義:
為子系統中的一組接口提供一個一致(穩定)的界面,Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用(復用)。
Fa?ade的結構:
Fa?ade更多的是表達的一種設計原則和思想。
要點總結:
<1>從客戶程序角度來看,Facade模式簡化了整個組建系統的接口,對于組建內部與外部客戶程序來說,達到了一種“解耦”的效果——內部子系統的任何變化不會影響到Facade接口的變化。
<2>Facade設計模式更注重從構架的層次去看整個系統,而不是單個類的層次,Facade很多時候更是一種架構設計的模式。
<3>Facade設計模式并非一個集裝箱,可以任意的放入任何多個對象。Facade模式中組件的內部應該是“相互耦合關系比較大的一系列組件”,而不是簡單的功能集合,以便能夠實現松耦合,高內聚的特性。
(2)代理模式
<1>代理模式的動機:
在面向對象系統中,有些對象由于某種原因(比如對象創建開銷很大或者某些操作需要安全控制或者需要進程外訪問等),直接訪問會給使用者、或者系統結構帶來很多的麻煩。
<2>代理模式的定義:
為其他對象提供一種代理以控制(隔離,使用接口)對這個對象的訪問。
client端:
class ISubject{
public:
virtual void process();
};
class RealSubject: public ISubject{
public:
virtual void process(){
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new RealSubject();
}
void DoTask(){
//...
subject->process();
//....
}
};
proxy端:
class SubjectProxy: public ISubject{
public:
virtual void process(){
//對RealSubject的一種間接訪問
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new SubjectProxy();
}
void DoTask(){
//...
subject->process();
//....
}
};
用戶程序不能直接訪問RealSubject對象,那么他需要使用一個代理類Proxy來代理的訪問RealSubject對象。
要點總結:
<1>“增加一層間接層”是軟件系統中對許多復雜問題的一種常見解決方案。在面向對象系統中,直接使用某些對象會帶來很多問題,作為間接層的Proxy對象便是解決這一問題的常用手段。
<2>具體Proxy設計模式的實現方法、實現粒度都相差很大,有些可能對單個對象做細粒度的控制,如copy-on-write技術,有些可能對組建模塊提供抽象代理層,在架構層次對對象做proxy。
<3>proxy并不一定要求保持接口完整的一致性,只要能夠實現間接控制,有時候損及一些透明性是可以接受的。
(3)適配器模式
<1>適配器模式的動機:
在軟件系統中,由于應用環境的變化,常常需要將“一些現存的對象”放在新的環境中應用,但是新環境要求的接口是這些現存對象所不滿足的。
<2>適配器模式的定義:
將一個類的接口轉換成客戶希望的另一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
//目標接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遺留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遺留類型
class OldClass: public IAdaptee{
//....
};
//對象適配器
class Adapter: public ITarget{ //繼承
protected:
IAdaptee* pAdaptee;//組合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//類適配器
class Adapter: public ITarget,
protected OldClass{ //多繼承
}
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
class stack{
deqeue container; // 底層容器
};
class queue{
deqeue container; // 底層容器
};
要點總結:
<1>Adapter模式主要應用于“希望復用一些現存的類,但是接口又與服用環境要求不一致的情況”,在遺留代碼復用、類庫遷移等方面非常有用。
<2>GoF23定義了兩種Adapter模式的結構實現:對象適配器和類適配器。但類適配器采用“多繼承”的實現方式,一般不推薦使用。對象適配器采用“對象組合”的方式,更符合松耦合的精神。
<3>Adapter模式可以實現的非常靈活,不必拘泥于GoF23中定義的兩種結構。例如,完全可以將Adapter模式中的“現存對象”作為新的接口方法參數,來達到適配的目的。
(4)中介者模式
<1>中介者模式的動機:
在軟件構建的過程中,經常出現多個對象互相關聯交互的情況,對象之間常常會維持一種復雜的引用關系,如果遇到了一些需求的更改,這種直接的引用關系將面臨不斷的變化。
在這種情況下,我們可以使用“中介對象”來管理對象間的關聯關系,避免相互交互的對象之間的緊耦合引用關系,從而更好地抵御變化。
<2>中介者模式的定義:
用一個中介對象來封裝(封裝變化)一系列的對象交互。中介者使各個對象不需要顯示的相互引用(編譯時依賴 -> 運行時依賴),從而使其耦合松散(管理變化),而且可以獨立的改變他們之間的交互。
上圖描述了依賴關系的解耦合過程。
通過中間的ConcreteMediator將ConcreteColleague1和ConcreteColleague2進行了依賴。從而使得ConcreteColleague1和ConcreteColleague2之間不在直接進行依賴。以此來進行解耦。
要點總結:
<1>將多個對象間復雜的關聯關系解耦,Mediator模式將多個對象間的控制邏輯進行集中管理,變“多個對象互相關聯”為“多個對象和一個中介者關聯”,簡化了系統的維護,抵御了可能的變化。
<2>隨著控制邏輯的復雜化,Mediator具體對象的實現可能相當復雜。這時候可以對Mediator對象進行分解處理。
<3>Facade模式是解耦系統間(單向)的對象關聯關系;Mediator模式是解耦系統內各個對象之間(雙向)的關聯關系。