設計模式 c++版(18)——門面模式
定義: 要求一個子系統的外部與其內部的通訊必須通過一個統一的物件進行。門面模式提供一個高層次的介面,使得子系統更易於使用(門面模式也叫做外觀模式)。
示例一:門面模式(通用版)
1. 類圖23-4
2. 類圖說明
Subsystem Classes 是子系統所有類的簡稱,它可以代表幾十個物件的集合。門面物件時外界訪問子系統內部的唯一通道。
3. 門面模式結構圖 23-5
4. 角色說明
Facade 門面角色。
客戶端可以呼叫這個角色的方法。此角色知曉子系統的所有功能和責任。一般情況下,本角色會將所有從客戶端發來的請求委派到響應的子系統去,也即該角色沒有實際的業務邏輯,只是一個委託類。
subsystem 子系統角色
可以同時有一個或多個子系統。每一個子系統都不是一個單獨的類,而是一個類的集合。子系統不知道門面的存在。對於子系統而言,門面僅僅是另一個客戶端而已。
5. 程式碼清單
#include <QCoreApplication> #include <QDebug> #include <QVector> //子系統 class ClassA { public: void doSomethingA() { qDebug() << "classA do something";} }; class ClassB { public: void doSomethingB() { qDebug() << "classB do something";} }; class ClassC { public: void doSomethingC() { qDebug() << "classC do something";} }; //門面物件 class Facade { public: void methodA() { this->m_classA.doSomethingA(); } void methodB() { this->m_classB.doSomethingB(); } void methodC() { this->m_classC.doSomethingC(); } private: ClassA m_classA; ClassB m_classB; ClassC m_classC; }; int main() { Facade face; face.methodA(); face.methodB(); face.methodC(); return 0; }
示例二:投遞信件
1. 類圖23-1(初版)
2. 程式碼清單(初版)
//寫信過程介面 class ILetterProcess { public: virtual void writeContext(QString context) = 0; //寫信內容 virtual void fillEnvelope(QString address) = 0; //寫信封 virtual void letterInotoEnvelope() = 0; //信放入信封 virtual void sendLetter() = 0; //郵遞 }; //寫信過程實現 class LetterProcessImpl:public ILetterProcess { public: virtual void writeContext(QString context) { qDebug() << "write " << context; } virtual void fillEnvelope(QString address) { qDebug() << "add " << address; } virtual void letterInotoEnvelope() { qDebug() << "put the letter into envelope"; } virtual void sendLetter() { qDebug() << "send Letter"; } }; int main() { ILetterProcess *letterProcess = new LetterProcessImpl(); letterProcess->writeContext("aaaaa"); letterProcess->fillEnvelope("sichuan"); letterProcess->letterInotoEnvelope(); letterProcess->sendLetter(); delete letterProcess; return 0; }
3. 問題:
使用者需要知道寫信內容,寫信封,信放入信封,郵遞,這四個步驟,而且還要知道他們的順序,一旦出錯,信就不可能郵寄出去。
4. 改善:
增加一個 ModenPostOffice 類,負責對一個比較複雜的信件處理過程的封裝,然後高層模組只要和它有互動就行。
5. 類圖23-2(改善版)
6. 程式碼清單(改善版)
//寫信過程介面
class ILetterProcess
{
public:
virtual void writeContext(QString context) = 0; //寫信內容
virtual void fillEnvelope(QString address) = 0; //寫信封
virtual void letterInotoEnvelope() = 0; //信放入信封
virtual void sendLetter() = 0; //郵遞
};
//寫信過程實現
class LetterProcessImpl:public ILetterProcess
{
public:
virtual void writeContext(QString context)
{
qDebug() << "write " << context;
}
virtual void fillEnvelope(QString address)
{
qDebug() << "add " << address;
}
virtual void letterInotoEnvelope()
{
qDebug() << "put the letter into envelope";
}
virtual void sendLetter()
{
qDebug() << "send Letter";
}
};
class ModenPostOffice
{
public:
ModenPostOffice()
{
this->m_letterProcess = new LetterProcessImpl();
}
~ModenPostOffice()
{
delete this->m_letterProcess;
}
void sendLetter(QString context, QString address)
{
this->m_letterProcess->writeContext(context);
this->m_letterProcess->fillEnvelope(address);
this->m_letterProcess->letterInotoEnvelope();
this->m_letterProcess->sendLetter();
}
private:
ILetterProcess *m_letterProcess;
};
int main()
{
ModenPostOffice office;
QString address = "sichuan";
QString context = "aaaa";
office.sendLetter(context, address);
return 0;
}
擴充套件性提高了,例:增加一項安全檢查,增加了一個Police類,負責對信件進行檢查
7. 類圖23-3(擴充套件版)
8. 程式碼清單(擴充套件版)
//寫信過程介面
class ILetterProcess
{
public:
virtual void writeContext(QString context) = 0; //寫信內容
virtual void fillEnvelope(QString address) = 0; //寫信封
virtual void letterInotoEnvelope() = 0; //信放入信封
virtual void sendLetter() = 0; //郵遞
};
//寫信過程實現
class LetterProcessImpl:public ILetterProcess
{
public:
virtual void writeContext(QString context)
{
qDebug() << "write " << context;
}
virtual void fillEnvelope(QString address)
{
qDebug() << "add " << address;
}
virtual void letterInotoEnvelope()
{
qDebug() << "put the letter into envelope";
}
virtual void sendLetter()
{
qDebug() << "send Letter";
}
};
class Police
{
public:
void checkLetter(ILetterProcess *letterProcess)
{
qDebug() << "is safe";
}
};
class ModenPostOffice
{
public:
ModenPostOffice()
{
this->m_letterProcess = new LetterProcessImpl();
}
~ModenPostOffice()
{
delete this->m_letterProcess;
}
void sendLetter(QString context, QString address)
{
this->m_letterProcess->writeContext(context);
this->m_letterProcess->fillEnvelope(address);
this->m_letterPolice.checkLetter(this->m_letterProcess);
this->m_letterProcess->letterInotoEnvelope();
this->m_letterProcess->sendLetter();
}
private:
ILetterProcess *m_letterProcess;
Police m_letterPolice;
};
int main()
{
ModenPostOffice office;
QString address = "sichuan";
QString context = "aaaa";
office.sendLetter(context, address);
return 0;
}
三、門面模式的應用
1. 優點:
- 減少系統的相互依賴。如果不使用門面模式,外界訪問直接深入到子系統內部,相互之間是強耦合關係。門面模式使得所有的依賴都是對門面物件依賴,與子系統無關。
- 提高了靈活性。不論子系統內部如何變化,只要不影響到門面物件即可。
- 提高安全性。想讓外部訪問子系統的哪些業務就開通哪些邏輯,不在門面上開通的方法,無法訪問到。
2. 缺點:
對修改關閉,對擴充套件開放。一旦系統投產後發現有錯誤,唯一能做的就是修改門面角色的程式碼,這個風險相當大,需要在設計的時候慎重思考。
3. 使用場景:
- 為一個複雜的模組或子系統提供一個供外界訪問的介面。
- 子系統相對獨立。外界對子系統的訪問只要黑箱操作即可。例:利息計算問題,對於使用該系統的開發人員來說,需要做的是輸入金額及存期,其他不用關心,返回結果是利息,這時候,門面模式非用不可。
- 預防低水平人員帶來的風險擴散。當使用低水平的技術人員參與專案開發時,為降低個人程式碼質量對整體專案的影響風險,一般做法“畫地為牢”,只能在指定的子系統中開發,然後再提供門面介面進行訪問操作。
4. 注意事項:
①一個子系統可以有多個門面。
- 一般情況下,一個子系統只要一個門面就夠了,以下幾種情況需要子系統有多個門面:
- 門面已經龐大到不能承受的程度。例:一個門面物件已經超過了200行程式碼,雖然都是簡單的委託,也建議拆分成多個門面,可按照功能拆分。比如資料庫操作門面可拆分為,查詢門面、刪除門面、更新門面等。
- 子系統可以提供不同訪問路徑。
②門面不參與子系統內的業務邏輯
四、最佳實踐
當一個子系統比較複雜時,比如演算法或者業務比較複雜,就可以封裝出一個或多個門面出來,專案的結構簡單,而且擴充套件性非常好。另外,對於一個較大專案,為了避免人員帶來的風險,也可以使用門面模式,技術水平較差的成員,儘量安排獨立的模組,然後把他寫的程式封裝到一個門面裡,儘量讓其他專案成員不用看到這些人的程式碼。使用門面模式後,對門面進行單元測試,約束專案成員的程式碼質量。
參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社