引子:牛市還會虧錢的散戶#

小菜公司同事顧韻梅在大牛市裡卻天天虧錢:

  • 看好一隻快漲停的股票買進,第二天就跌
  • 換另一隻好股票,幾天不漲
  • 一賣出,馬上漲停

不會炒股票卻急著進場,買進賣出頻繁——這是典型新股民特徵。

散戶一打開股票軟體,一千多支股票紅紅綠綠、又是 K 線又是基本面,頭暈眼花、迷茫困惑

基金的解法#

基金(Fund):將投資者分散的資金集中起來,交由專業的經理人進行管理,投資於股票、債券、外匯等領域。

投資者不必直接面對上千支股票,只需關心基金的漲跌,由基金經理人代為操作。

對應到軟體:

  • 投資者直接買股票 = 客戶端與所有具體類別都耦合 → 耦合度太高
  • 投資者買基金 = 客戶端只與一個簡化介面互動 → 耦合度大幅降低

第一版:股民直接炒股的程式#

class Stock1   { public void Sell() { ... } public void Buy() { ... } }
class Stock2   { /* 同 */ }
class Stock3   { /* 同 */ }
class NationalDebt1 { /* 國債 */ }
class Realty1  { /* 房地產 */ }

客戶端:

Stock1 gu1 = new Stock1();
Stock2 gu2 = new Stock2();
Stock3 gu3 = new Stock3();
NationalDebt1 nd1 = new NationalDebt1();
Realty1 rt1 = new Realty1();

gu1.Buy(); gu2.Buy(); gu3.Buy();
nd1.Buy(); rt1.Buy();

gu1.Sell(); gu2.Sell(); gu3.Sell();
nd1.Sell(); rt1.Sell();

客戶端必須認識所有投資產品類別,需要參與每一筆買賣的細節。

第二版:投資基金的程式#

加入 Fund 類別把所有產品包起來:

class Fund
{
    Stock1 gu1; Stock2 gu2; Stock3 gu3;
    NationalDebt1 nd1; Realty1 rt1;

    public Fund()
    {
        gu1 = new Stock1();
        gu2 = new Stock2();
        gu3 = new Stock3();
        nd1 = new NationalDebt1();
        rt1 = new Realty1();
    }

    public void BuyFund()  { gu1.Buy();  gu2.Buy();  gu3.Buy();  nd1.Buy();  rt1.Buy();  }
    public void SellFund() { gu1.Sell(); gu2.Sell(); gu3.Sell(); nd1.Sell(); rt1.Sell(); }
}

客戶端:

Fund jijin = new Fund();
jijin.BuyFund();
jijin.SellFund();

客戶端不再需要了解股票、國債、房地產的細節——買了基金回家睡覺,由基金經理人完成所有操作。

外觀模式#

外觀模式(Facade Pattern),又稱門面模式:為子系統中的一組介面提供一個一致的界面,此模式定義了一個高層介面,使得子系統更容易使用。[DP]

結構#

  • Facade(外觀):知道哪些子系統類別負責處理請求,將客戶請求代理給適當的子系統物件
  • SubSystem Classes(子系統類別群):實現子系統功能,處理 Facade 物件指派的工作;對 Facade 沒有任何認識,子系統不知道 Facade 的存在
classDiagram
    class Client
    class Facade {
        +MethodA()
        +MethodB()
    }
    class SubSystemOne {
        +MethodOne()
    }
    class SubSystemTwo {
        +MethodTwo()
    }
    class SubSystemThree {
        +MethodThree()
    }
    class SubSystemFour {
        +MethodFour()
    }
    Client --> Facade
    Facade --> SubSystemOne
    Facade --> SubSystemTwo
    Facade --> SubSystemThree
    Facade --> SubSystemFour
class SubSystemOne   { public void MethodOne()   { ... } }
class SubSystemTwo   { public void MethodTwo()   { ... } }
class SubSystemThree { public void MethodThree() { ... } }
class SubSystemFour  { public void MethodFour()  { ... } }

class Facade
{
    SubSystemOne one;
    SubSystemTwo two;
    SubSystemThree three;
    SubSystemFour four;

    public Facade()
    {
        one = new SubSystemOne();
        two = new SubSystemTwo();
        three = new SubSystemThree();
        four = new SubSystemFour();
    }

    public void MethodA() { one.MethodOne(); two.MethodTwo(); four.MethodFour(); }
    public void MethodB() { two.MethodTwo(); three.MethodThree(); }
}

客戶端只與 Facade 互動:

Facade facade = new Facade();
facade.MethodA();
facade.MethodB();

外觀模式完美體現了依賴倒轉原則迪米特法則——它是非常常用的模式之一。

何時使用外觀模式#

1. 設計初期:層與層之間建立外觀#

經典三層架構(資料訪問層 / 業務邏輯層 / 表示層)之間應該建立外觀(Facade)。

為複雜的子系統提供簡單介面,使層與層的耦合大幅降低

2. 開發階段:抑制子系統膨脹#

子系統會因為不斷重構而變得越來越複雜,大多模式使用時都會產生許多小類別。

  • 這些小類別本身是好事,但會讓外部呼叫的客戶程式不容易使用
  • 增加 Facade 提供簡單介面,減少類別之間的依賴

3. 維護遺留系統:作為新舊橋樑#

維護一個非常難以擴展但仍包含重要功能的遺留系統時,可以為新系統開發一個 Facade:

  • 新系統與 Facade 互動
  • Facade 處理與遺留程式碼的所有複雜工作[R2P]

兩個小組分工:

  • 一組開發 Facade 與老系統的交互
  • 另一組只需了解 Facade 的介面,直接開發新系統呼叫

這能減少很多不必要的麻煩。