設計模式(10) 外觀模式(FACADE)

問題聚焦:
    設計一個系統時,比較常見的做法是將它劃分爲若干個小的系統,互相協作,組成一個大的系統。
    那麼,在設計這些小系統時,需要注意一些什麼呢?
    外觀模式爲什麼可以讓子系統更加容易的使用呢?(最小化子系統之間的通信和相互依賴關係,即降低耦合度)


意圖:
    爲子系統中的一組接口提供一個一致的界面。Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。

動機:
    將一個系統劃分爲若干個子系統有利於降低系統的複雜性。
    一個常見的設計目標是使子系統間的通信和相互依賴關係達到最小。
    達到該目標的途徑之一就是引入一個外觀對象,它爲子系統中較一般的設施提供了一個單一而簡單的界面。
    其思想如下圖所示:
    
    Demo:以編譯器子系統的設計爲例,說明外觀模式的思想:
    子系統類劃分:Scanner, Parser, ProgramNode, BytecodeStream, ProgramNodeBuilder
    要求:對普通用戶提供一個高層的,單一而簡單的編譯子系統接口
              對高級專業的用戶提供底層功能,可以直接訪問具體的子系統類。
    設計:
    
    Compiler類(關鍵),其職責爲兩點:    
    1)爲用戶屏蔽子系統,提供一個高層的簡單的使用接口;
    2)不完全隱藏實現編譯功能的那些子系統類,使得少數懂的如何使用底層功能的人可以調用這些功能。
    

適用性:
在遇到以下情況使用Facade模式:
  • 爲一個複雜子系統提供一個簡單接口。
  • 客戶程序與抽象類的實現部分之間存在很大的依賴性。
  • 當你需要構件一個層次結構的子系統時,使用facade模式定義子系統中每層的入口點。如果子系統之間是相互依賴的,可以讓它們僅通過facade進行通訊,從而簡化了它們之間的依賴關係。
結構:

參與者:
  • Facade(Compiler):
    • 知道哪些子系統類負責處理請求;
    • 將客戶的請求代理給適當的子系統對象。
  • Subsystem classes(Scanner, Parser, ProgramNode):
    • 實現子系統的功能;
    • 處理由Facade對象指派的任務;
    • 沒有Facade的任何相關信息。
協作:
  • 客戶程序通過發送請求給Facade與子系統通訊,Facade將這些消息轉發給適當的子系統對象。
  • 使用Facade的客戶程序不需要直接訪問子系統對象
效果(優點):
  1. 對客戶屏蔽子系統組件,減少了客戶處理的對象的數目,並使得子系統使用起來更加方便
  2. 實現了子系統與客戶之間的耦合關係,而子系統內部的功能組件往往是緊耦合的。鬆耦合關係使得子系統的組件變化不會影響到它的客戶。具體如下:
    • Facade模式有助於建立層次結構系統
    • 有助於對對象之間的依賴關係分層
    • 消除複雜的循環依賴關係
    • 降低編譯依賴性,限制重要系統中較小的變化所需要的重編譯工作
  3. 有利於簡化系統在不同平臺之間的移植過程
  4. 如果應用需要,它並不限制它們使用子系統類。因此你可以在系統易用性和通用性之間加以選擇。


實現:
  • 降低客戶-子系統之間的耦合度:兩種方式
    •     用抽象類實現Facade而它的具體子類對應不同的子系統實現,這可以進一步降低客戶與子系統的耦合度
    •     用不同的子系統對象配置Facade,僅需要對它的子系統對象進行替換即可
  • 公共子系統類與私有子系統類:
    •     用類的思想思考子系統:一個子系統與一個類的相似之處是,它們都有接口並且它們都封裝了一些東西——類封裝了狀態和操作,而子系統封裝了一些類。
    •     子系統的公共接口包含所有的客戶程序可以訪問的類
    •     子系統的私有接口僅用於對子系統進行擴充和維護
    •     Facade類是公共接口的一部分,但它不是唯一的部分,子系統的其他部分通常也是公共的
代碼示例:
以上面所提到的編譯器子系統爲例。
類設定:
BytecodeStream類,實現了一個Bytecode對象流。
Bytecode對象封裝了一個字節碼,這個字節碼可用於指定機器指令。
Token類,封裝了編程語言中的標識符
Scanner類,接收字符流併產生一個標識符流,一次產生一個標識符(token)。
class Scanner 
{
public:
    Scanner(istream&);
    virtual ~Scanner();

    virtual Token& Scan();

private:
    istream& _inputStream;
};
Parser:Parser類由Scanner生成的標識符構件一棵語法分析樹。
class Parser
{
public:
    Parser();
    virtual ~Parser();

    virtual void Parse(Scanner&, ProgramNodeBuilder&);
};
ProgramNodeBuilder:建立語法樹,遵循Builder模式的交互式操作。
class ProgramNodeBuilder
{
public:
    ProgramNodeBuilder();

    virtual ProgramNode* NewVariable (
         const char* variableName
     ) const;

    virtual ProgramNode* NewAssignment (
        ProgramNode* variable, ProgramNode* expression
    ) const;

    virtual ProgramNode* NewRetrurnStatement (
        ProgramNode* value
    ) const;

    virtual ProgramNode* NewCondition (
        ProgramNode* condition, 
        ProgramNode* truePart, 
        ProgramNode* falsePart
    ) const;

    ProgramNode* _node;

private:
    ProgramNode* _node;
};
ProgramNode:語法樹由ProgramNode子類(StatementNode和ExpressionNode)的實例構成,定義了一個接口用於操作程序節點和它的子節點。ProgramNode層次結構是組合模式(Composite)的一個應用實例。
class ProgramNode {
public:
    virtual void GetSourcePositon(int& line, int& index);

    virtual void Add(ProgramNode*);
    virtual void Remove(ProgramNode*);
    
    virtual void Traverse(CodeGenerator&);
protected:
    ProgramNode();
};
Traverse:以一個CodeGenerator對象爲參數,ProgramNode子類使用這個對象產生機器代碼,機器代碼格式爲BytecodeStream中的ByteCode對象。
CodeGenerator:一個訪問者(訪問者模式),有兩個子類StackMachineCodeGenerator和RISCCodeGenerator,分別爲不同的硬件體系結構生成機器代碼。
class CodeGenerator {
public:
    virtual void Visit(StatementNode*);
    virtual void Visit(ExpressionNode*);

protected:
    CodeGenerator(BytecodeStream&);
protected:
    BytecodeStream& _output;
};
ExpressionNode:ProgramNode的每個子類在實現Traverse時,對它的ProgramNode子對象調用Traverse。每個子類一次對它的子節點做同樣的動作。這樣遞歸下去。
void ExpressionNode::Traverse (CodeGenerator& cg)
 {
    cg.Visit(this);
    ListIterator<ProgramNode*> i(_children);

    for (i.First(); !i.IsDone(); i.Next()) 
    {
        i.CurrentItem()->Traverse(cg);
    }
}
上面這些類構成了編譯子系統。
引用Compiler類,將所有的部件集成在一起。Compiler提供了一個簡單的接口爲特定的機器編譯源代碼並生成可執行代碼。
class Compiler
{
public:
    Compiler();
    virtual void Compile(istream&, BytecodeStream);
};

void Compiler::Compile(istream& input, BytecodeStream& output)
{
    Scanner scanner(input);
    ProgramNodeBuilder builder;
    Parser parser;

    parser.Parse(scanner, builder);
    RISCCodeGenerator generator(builder);
    ProgramNode* parseTree = builder.GetRootNode();
    parserTree->Traverse(generator);
}


相關模式:
抽象工廠模式可以與Facade模式一起使用提供一個接口,這一接口可用來以一種子系統獨立的方式創建子系統對象。抽象工廠模式也可以代替Facade模式隱藏那些與平臺相關的類。
Mediator模式,通常集中不屬於任何單個對象的功能。Facade模式不定義新功能,子系統也不知道facade的存在
通常僅需要一個Facade對象,因此Facade對象通常屬於Singleton模式



參考資料:
《設計模式:可複用面向對象軟件的基礎》




發佈了95 篇原創文章 · 獲贊 45 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章