對象創建
- 前言
- 1. Factory Method
- 1.1 模式介紹
- 1.2 模式代碼
- 1.2.1 問題代碼
- 1.2.2 重構代碼
- 1.3 模式類圖
- 1.4 要點總結
- 2. Abstract Factory
- 2.1 模式介紹
- 2.2 模式代碼
- 2.2.1 問題代碼
- 2.2.2 重構代碼
- 2.3 模式類圖
- 2.4 要點總結
- 3. Prototype
- 3.1 模式介紹
- 3.2 模式代碼
- 3.3 模式類圖
- 3.4 要點總結
- 4. Builder
- 4.1 模式介紹
- 4.2 模式代碼
- 4.3 模式類圖
- 4.4 要點總結
前言
“對象創建”模式:
通過“對象創建” 模式繞開new,來避免對象創建(new)過程中所導致的緊耦合(依賴具體類),從而支持對象創建的穩定。它是接口抽象之后的第一步工作。
典型模式:
- Factory Method 工廠方法
- Abstract Factory 抽象工廠
- Prototype 原型模式
- Builder 構建器模式
1. Factory Method
1.1 模式介紹
動機:在軟件系統中,經常面臨著創建對象的工作;由于需求的變化,需要創建的對象的具體類型經常變化。
如何應對這種變化?如何繞過常規的對象創建方法(new),提供一種“封裝機制”來避免客戶程序和這種“具體對象創建工作”的緊耦合?
定義一個用于創建對象的接口,讓子類決定實例化哪一個類。Factory Method使得一個類的實例化延遲(目的:解耦,手段:虛函數)到子類。
——《設計模式》GoF
1.2 模式代碼
1.2.1 問題代碼
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{};class MainForm : public Form
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){ISplitter * splitter=new BinarySplitter();//依賴具體類splitter->split();}
};
你有一個按鈕,點擊按鈕以后可以對二進制文件、文本文件、圖片文件、視頻文件進行分割,如果按照上述代碼進行編寫,其依賴具體類,如果你想增加其他文件格式方法時,得修改button里的方法,這會使得封裝性被破壞
1.2.2 重構代碼
//抽象類
class ISplitter{public:virtual void split()=0;virtual ~ISplitter(){}};//工廠基類
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0;virtual ~SplitterFactory(){}
};//具體類
class BinarySplitter : public ISplitter{virtual void split(){}};class TxtSplitter: public ISplitter{virtual void split(){}
};class PictureSplitter: public ISplitter{virtual void split(){}
};class VideoSplitter: public ISplitter{virtual void split(){}
};//具體工廠
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();}
};class MainForm : public Form
{SplitterFactory* factory;//工廠public:MainForm(SplitterFactory* factory){this->factory=factory;}void Button1_Click(){ISplitter * splitter=factory->CreateSplitter(); //多態newsplitter->split();}
};
解決方法:設計一個工廠基類,聲明一個創建對象的接口,對象基礎工廠基類,實現創建對象的方法,即可實現運行時綁定
1.3 模式類圖
1.4 要點總結
- Factory Method模式用于隔離類對象的使用者和具體類型之間的耦合關系。面對一個經常變化的具體類型,緊耦合關系(new)會導致軟件的脆弱。
- Factory Method模式通過面向對象的手法,將所要創建的具體對象工作延遲到子類,從而實現一種擴展(而非更改)的策略,較好地解決了這種緊耦合關系。
- Factory Method模式解決“單個對象”的需求變化。缺點在于要求創建方法/參數相同。
2. Abstract Factory
2.1 模式介紹
動機:在軟件系統中,經常面臨著“一系列相互依賴的對象”的創建工作;同時,由于需求的變化,往往存在更多系列對象的創建工作。
如何應對這種變化?如何繞過常規的對象創建方法(new),提供一種“封裝機制”來避免客戶程序和這種“多系列具體對象創建工作”的緊耦合?
提供一個接口,讓該接口負責創建一系列“相關或者相互依賴的對象”,無需指定它們具體的類。
——《設計模式》GoF
2.2 模式代碼
抽象工廠是工廠方法模式的子集,當要創建的對象之間有關聯時才使用抽象工廠
2.2.1 問題代碼
假設現在要實現SQL處理的功能
問題代碼1:直接將類型硬編碼到功能里,如果有多種數據庫,比如MySQL、Oracle等等,就會不利于擴展
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()){}}
};
問題代碼2:在問題代碼1的基礎上,使用工廠方法解決依賴具體類問題
//數據庫訪問有關的基類
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()){}}
};
如果傳遞的dbConnectionFactory、dbCommandFactory、dataReaderFactory不是同一系列的就會出問題,例如一部分是MySQL對象,一部分是Oracle對象
2.2.2 重構代碼
//數據庫訪問有關的基類
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()){}}
};
將一系列方法封裝在一起,這便是抽象工廠模式
2.3 模式類圖
2.4 要點總結
- 如果沒有應對“多系列對象構建”的需求變化,則沒有必要使用Abstract Factory模式,這時候使用簡單的工廠完全可以。
- “系列對象”指的是在某一特定系列下的對象之間有相互依賴、或作用的關系。不同系列的對象之間不能相互依賴。
- Abstract Factory模式主要在于應對“新系列”的需求變動。其缺點在于難以應對“新對象”的需求變動。
3. Prototype
3.1 模式介紹
原型是一種創建型設計模式,它允許您復制現有對象,而不使您的代碼依賴于它們的類。
問題:
假設你有一個對象,你想創建它的一個精確副本。你會怎么做?首先,你必須創建一個相同類的新對象。然后你必須遍歷原始對象的所有字段并將其值復制到新對象。
很好!但是有一個問題。并非所有對象都可以通過這種方式復制,因為某些對象的字段可能是私有的,從對象本身外部不可見。
直接方法還有一個問題。由于您必須知道對象的類才能創建副本,因此您的代碼將依賴于該類。如果額外的依賴關系不嚇到您,那么還有另一個問題。有時您只知道對象遵循的接口,但不知道其具體類,例如,當方法中的參數接受遵循某個接口的任何對象時。
3.2 模式代碼
//抽象類
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);}virtual void split(){}
};class TxtSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new TxtSplitter(*this);}virtual void split(){}
};class PictureSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new PictureSplitter(*this);}virtual void split(){}
};class VideoSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new VideoSplitter(*this);}virtual void split(){}
};class MainForm : public Form
{ISplitter* prototype;//原型對象public:MainForm(ISplitter* prototype){this->prototype=prototype;}void Button1_Click(){ISplitter * splitter=prototype->clone(); //克隆原型splitter->split();}
};
3.3 模式類圖
3.4 要點總結
- 您可以克隆對象而不與其具體類耦合。
- 您可以擺脫重復的初始化代碼,轉而克隆預先構建的原型。
- 您可以更加方便地制作復雜的物體。
- 處理復雜對象的配置預設時,您可以獲得繼承的替代方法。
4. Builder
4.1 模式介紹
動機:在軟件系統中,有時候面臨著“一個復雜對象”的創建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的算法卻相對穩定。
如何應對這種變化?如何提供一種“封裝機制”來隔離出“復雜對象的各個部分”的變化,從而保持系統中的“穩定構建算法”不隨著需求改變而改變?
將一個復雜對象的構建與其表示相分離,使得同樣的構建過程(穩定)可以創建不同的表示(變化)。
——《設計模式》GoF
4.2 模式代碼
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();}
};
構建器HouseBuilder
負責定義構建House
時每個步驟的具體接口(變化)和管理正在構建的對象,由子類繼承并實現接口;
HouseDirector
負責實現House
在構建時的整體流程(不變)
4.3 模式類圖
4.4 要點總結
- Builder 模式主要用于“分步驟構建一個復雜的對象”。在這其中“分步驟”是一個穩定的算法,而復雜對象的各個部分則經常變化。
- 變化點在哪里,封裝哪里—— Builder模式主要在于應對“復雜對象各個部分”的頻繁需求變動。其缺點在于難以應對“分步驟構建算法”的需求變動。
- 在Builder模式中,要注意不同語言中構造器內調用虛函數的差別(C++ vs. C#) 。