---
tags: 設計模式
---
# 外觀模式(Facade Pattern)
## 前言
- 當原本的介面太難用了, 我們可以重新定義一個介面, 供外部使用
- Facade 滿足最小知識原則.
- Facade 的重點在 "提供一個簡單易使用的公開接口介面"
#### 問題需求
假設現實生活中, 你需要實作一個按鈕讓使用者(們)可以透過微波爐微波食物. 此時你可能需要使用到三個模組的部分功能, 並且依照**正確的順序**使用它們, 否則微波爐會爆炸XD
1. 啟動電源(電源供應模組)
1. 加熱食物(加熱模組)
1. 冷卻機器(冷卻模組)
```mermaid
graph TB;
美女
宅男
小屁孩
其他沒用到微波爐的使用者
美女--使用-->電源供應模組
美女--使用-->加熱模組
美女--使用-->冷卻模組
宅男--使用-->電源供應模組
宅男--使用-->加熱模組
宅男--使用-->冷卻模組
小屁孩--使用-->電源供應模組
小屁孩--使用-->加熱模組
小屁孩--使用-->冷卻模組
subgraph
電源供應模組
加熱模組
冷卻模組
其他跟微波爐無關的模組
end
```
##### 可能實作程式碼
```C#
public class 美女{
public void 美女操作微波爐(){
var 電源 = new 電源供應模組();
電源.供電();
new 加熱模組().加熱();
new 冷卻模組().冷卻();
電源.斷電();
}
public void 美女化妝() => throw new NotImplementedException();
}
public class 宅男{
public void 宅男操作微波爐(){
var 電源 = new 電源供應模組();
電源.供電();
new 加熱模組().加熱();
new 冷卻模組().冷卻();
電源.斷電();
}
public void 宅男攤在沙發上看電視() => throw new NotImplementedException();
}
public class 小屁孩{
public void 小屁孩操作微波爐(){
var 電源 = new 電源供應模組();
電源.供電();
new 加熱模組().加熱();
new 冷卻模組().冷卻();
電源.斷電();
}
public void 小屁孩玩耍() => throw new NotImplementedException();
}
```
##### 問題
- 使用者(們)必須認識電源供應模組、加熱模組、冷卻模組, 並且知道這三個模組的正確使用順序, 才能夠正確使用微波爐的功能.
- 使用者(們)直接依賴三個模組, 增加日後修改的困難. 比如說 : 若日後需要抽換加熱模組, 就必須修改每一個加熱模組被使用的地方, (e.g. 將加熱模組換成美國製造的加熱模組)
```C#
// 將 new 加熱模組().加熱();
// 換成 new 加熱模組_美國製造().加熱();
```
## Facade 介紹
#### 定義
> Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
> Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
> * Applicability
> * Need to provide a simple interface to a complex system
> * Need to decouple a subsystem from its clients
> * Need to provide an interface to a software layer
> * Consequences
> * Shields clients from subsystem components
> * Promotes weak coupling between the subsystem and its clients
> * Facade doesn't prevent clients from using subsystem classes if they need to
- 為複雜的子系統定義一組統一的介面/接口/Facade , 使用者可透過此介面/接口/Facade 更容易操作子系統
- 子系統可以表示為一個複雜的類別、一個模組(包含多個類別)等等.
#### 子系統定義
- 真正功能被實作處
- 不會和 Facade 有循環參考的問題
- 子系統用到 Facade, Facade 又用到子系統
- 通常已經存在且有一定程度的穩定性
#### Facade 定義
- 介於 Client 和子系統之間, 以作為 Client 與子系統之間的溝通橋樑
- 知道子系統的公開介面
- 通常是類別, 有時候也可能是介面或抽象類別(當對外的 Facade 需要抽象的時候, 使用).
#### Structure Illustrate

```mermaid
graph LR;
Client--使用-->統一介面/接口/Facade
統一介面/接口/Facade -- 使用 --> 類別1
統一介面/接口/Facade -- 使用 --> 類別2
統一介面/接口/Facade -- 使用 --> 類別3
subgraph 複雜子系統
類別1
類別2
類別3
沒被使用到的類別...
end
```
## Code Example
### 複雜模組, 透過 Facade 簡化操作
```C#
public class 電源供應模組
{
public void 供電() => throw new NotImplementedException();
public void 斷電() => throw new NotImplementedException();
}
public class 加熱模組
{
public void 加熱() => throw new NotImplementedException();
}
public class 冷卻模組
{
public void 冷卻() => throw new NotImplementedException();
}
// Facade
public class 微波爐
{
private readonly 電源供應模組 _電源供應模組 = new 電源供應模組();
private readonly 加熱模組 _加熱模組 = new 加熱模組();
private readonly 冷卻模組 _冷卻模組 = new 冷卻模組();
public void 微波()
{
_電源供應模組.供電();
_加熱模組.加熱();
_冷卻模組.冷卻();
_電源供應模組.斷電();
}
}
public class 美女
{
private readonly 微波爐 _微波爐 = new 微波爐();
public void 操作微波爐() => _微波爐.微波();
public void 美女化妝() => throw new NotImplementedException();
}
public class 宅男{
private readonly 微波爐 _微波爐 = new 微波爐();
public void 操作微波爐() => _微波爐.微波();
public void 宅男攤在沙發上看電視() => throw new NotImplementedException();
}
public class 小屁孩{
private readonly 微波爐 _微波爐 = new 微波爐();
public void 操作微波爐() => _微波爐.微波();
public void 小屁孩玩耍() => throw new NotImplementedException();
}
```
- Client 依賴於微波爐, 而非依賴於電源供應模組、加熱模組、冷卻模組, Client 類別並不需要認知到這三個模組, 更不需要知道如何使用正確這些模組的邏輯
- 未來需要抽換電源供應模組、加熱模組、冷卻模組, 僅需要修改微波爐類別, 而不需要修改 Client 類別.
### 複雜類別, 透過 Facade 簡化操作
```C#
// God Class : 職責過多的類別.
public class GodClass
{
public void DoSomeThing1() => throw new NotImplementedException();
public void DoSomeThing2() => throw new NotImplementedException();
public void DoSomeThing3() => throw new NotImplementedException();
public void DoSomeThing4() => throw new NotImplementedException();
public void DoSomeThing5() => throw new NotImplementedException();
public void DoSomeThing6() => throw new NotImplementedException();
public void DoSomeThing7() => throw new NotImplementedException();
public void DoSomeThing8() => throw new NotImplementedException();
public void DoSomeThing9() => throw new NotImplementedException();
public void DoSomeThing10() => throw new NotImplementedException();
// 以下略
}
// 實務上, 只需要使用到 God Class 的其中兩個方法.
public class Facade1
{
private readonly GodClass _godClass = new GodClass();
public void DoSomeThing1() => _godClass.DoSomeThing1();
public void DoSomeThing2() => _godClass.DoSomeThing2();
}
// 實務上, 只需要使用到 God Class 的其中三個方法.
public class Facade2
{
private readonly GodClass _godClass = new GodClass();
public void DoSomeThing3() => _godClass.DoSomeThing3();
public void DoSomeThing4() => _godClass.DoSomeThing4();
public void DoSomeThing5() => _godClass.DoSomeThing5();
}
```
- 有時候我們沒辦法修改 God Class 的程式碼, 但 God Class 的使用又太過於複雜. 這時候可以為其建立一些 Facade, 讓 Client 端使用. 日後若 God Class 被重構, 也僅需要修改 Facade, 而不需要修改 Client 端.
- 改變客戶端所依賴的類別, 讓其不再依賴於 God Class, 方便日後重構.
### Additional Facade
```C#
public class SubClass
{
public void DoSomeThing1() => throw new NotImplementedException();
// 以下略
}
public class SubClass2
{
public void DoSomeThing2() => throw new NotImplementedException();
// 以下略
}
public class SubClass3
{
public void DoSomeThing3() => throw new NotImplementedException();
// 以下略
}
public class SubClass4
{
public void DoSomeThing4() => throw new NotImplementedException();
// 以下略
}
public class SubClass5
{
public void DoSomeThing5() => throw new NotImplementedException();
// 以下略
}
public class SubClass6
{
public void DoSomeThing6() => throw new NotImplementedException();
// 以下略
}
public class SubClass6
{
public void DoSomeThing6() => throw new NotImplementedException();
// 以下略
}
public class SubClass7
{
public void DoSomeThing7() => throw new NotImplementedException();
// 以下略
}
public class SubClass8
{
public void DoSomeThing8() => throw new NotImplementedException();
// 以下略
}
public class SubClass9
{
public void DoSomeThing9() => throw new NotImplementedException();
// 以下略
}
```
##### GodFacade
```C#
public class GodFacade
{
private readonly SubClass _subClass;
private readonly SubClass2 _subClass2;
private readonly SubClass3 _subClass3;
private readonly SubClass4 _subClass4;
private readonly SubClass5 _subClass5;
private readonly SubClass6 _subClass6;
private readonly SubClass7 _subClass7;
private readonly SubClass8 _subClass8;
private readonly SubClass9 _subClass9;
public GodFacade(SubClass subClass, SubClass2 subClass2, SubClass3 subClass3
, SubClass4 subClass4, SubClass5 subClass5, SubClass6 subClass6
, SubClass7 subClass7, SubClass8 subClass8, SubClass9 subClass9)
{
_subClass = subClass;
_subClass2 = subClass2;
_subClass3 = subClass3;
_subClass4 = subClass4;
_subClass5 = subClass5;
_subClass6 = subClass6;
_subClass7 = subClass7;
_subClass8 = subClass8;
_subClass9 = subClass9;
}
public void DoSomeThing1() => _subClass.DoSomeThing1();
public void DoSomeThing2() => _subClass2.DoSomeThing2();
public void DoSomeThing3() => _subClass3.DoSomeThing3();
public void DoSomeThing4() => _subClass4.DoSomeThing4();
public void DoSomeThing5() => _subClass5.DoSomeThing5();
public void DoSomeThing6() => _subClass6.DoSomeThing6();
public void DoSomeThing7() => _subClass7.DoSomeThing7();
public void DoSomeThing8() => _subClass8.DoSomeThing8();
public void DoSomeThing9() => _subClass9.DoSomeThing9();
}
```
##### AdditionalFacade
```C#
public class AdditionalFacade
{
private readonly SubClass _subClass;
private readonly SubClass2 _subClass2;
private readonly SubClass3 _subClass3;
private readonly SubClass4 _subClass4;
public AdditionalFacade(SubClass subClass, SubClass2 subClass2, SubClass3 subClass3
, SubClass4 subClass4)
{
_subClass = subClass;
_subClass2 = subClass2;
_subClass3 = subClass3;
_subClass4 = subClass4;
}
public void DoSomeThing1() => _subClass.DoSomeThing1();
public void DoSomeThing2() => _subClass2.DoSomeThing2();
public void DoSomeThing3() => _subClass3.DoSomeThing3();
public void DoSomeThing4() => _subClass4.DoSomeThing4();
}
public class AdditionalFacade2
{
private readonly SubClass5 _subClass5;
private readonly SubClass6 _subClass6;
private readonly SubClass7 _subClass7;
private readonly SubClass8 _subClass8;
private readonly SubClass9 _subClass9;
public AdditionalFacade2(SubClass5 subClass5, SubClass6 subClass6,
SubClass7 subClass7, SubClass8 subClass8, SubClass9 subClass9)
{
_subClass5 = subClass5;
_subClass6 = subClass6;
_subClass7 = subClass7;
_subClass8 = subClass8;
_subClass9 = subClass9;
}
public void DoSomeThing5() => _subClass5.DoSomeThing5();
public void DoSomeThing6() => _subClass6.DoSomeThing6();
public void DoSomeThing7() => _subClass7.DoSomeThing7();
public void DoSomeThing8() => _subClass8.DoSomeThing8();
public void DoSomeThing9() => _subClass9.DoSomeThing9();
}
// 此 Facade 可考慮刪除不使用.
public class Facade
{
private readonly AdditionalFacade _additionalFacade;
private readonly AdditionalFacade2 _additionalFacade2;
public Facade(AdditionalFacade additionalFacade, AdditionalFacade2 additionalFacade2)
{
_additionalFacade = additionalFacade;
_additionalFacade2 = additionalFacade2;
}
public void DoSomeThing1() => _additionalFacade.DoSomeThing1();
public void DoSomeThing2() => _additionalFacade.DoSomeThing2();
public void DoSomeThing3() => _additionalFacade.DoSomeThing3();
public void DoSomeThing4() => _additionalFacade.DoSomeThing4();
public void DoSomeThing5() => _additionalFacade2.DoSomeThing5();
public void DoSomeThing6() => _additionalFacade2.DoSomeThing6();
public void DoSomeThing7() => _additionalFacade2.DoSomeThing7();
public void DoSomeThing8() => _additionalFacade2.DoSomeThing8();
public void DoSomeThing9() => _additionalFacade2.DoSomeThing9();
}
```
- Facade 可能會變成 God Object, 耦合太多類別
- 將一些職責移到 AdditionalFacade, 讓 AdditionalFacade 可以減輕 GodFcade 問題的產生,
- AdditionalFacade 除了可以讓其他 Facade 使用外, 也可以讓 Client 端單獨使用.
### 類別操作不好用, 建立更好使用的 Facade 給 Client 使用
```C#
// 原本 Connecter 的使用方式是必須自己初始化相對應的屬性後, 再呼叫 Connect() 方法.
// 但使用者再使用時, 很容易忘記或是不知道要初始化屬性, 導致 Connect() 執行結果不符合預期.
public class Connecter
{
public int ConnectTime { get; set; }
public int Delay { get; set; }
public IPAddress SourceIpAddress { get; set; }
public IPAddress TargetIpAddress { get; set; }
public void Connect() => Console.WriteLine($"Delay {Delay} 秒後,
建立一條從 {SourceIpAddress} 到 {TargetIpAddress} 的連線, 持續 {ConnectTime}");
}
// 認為 Connecter 的實作不好用, 但又沒辦法修改或是沒時間等等理由.
// 建立 SimplyConnecter(Facade), 給使用者.
// SimplyConnecter 的使用方式不同於 Connecter, 僅需使用 Connect(Args) 方法, 並傳入對應的參數.
// 封裝正確使用 Connecter 的邏輯
public class SimplyConnecter
{
private Connecter _connecter;
public void Connect(int connectTime, int delay, string sourceIp, string targetIp)
{
_connecter = new Connecter
{
ConnectTime = connectTime,
Delay = delay,
SourceIpAddress = IPAddress.Parse(sourceIp),
TargetIpAddress = IPAddress.Parse(targetIp),
};
_connecter.Connect();
}
}
```
- 有時候我們會將一些操作比較不好使用的類別(通常不容易或是有一些歷史因素, 導致沒辦法修改), 重新封裝. 令其在操作上比較容易.
### 多墊一層, 日後抽換更方便
```C#
public class City
{
public string Name { get; set; }
public int Age { get; set; } = 3;
public override string ToString() => $"{Name} is {Age}";
}
// 將 Clone 的概念集中在 Clones.
// Facade
public class Clones
{
public static T CloneObj<T>(T obj)
{
var serialize = JsonSerializer.Serialize(obj);
return JsonSerializer.Deserialize<T>(serialize);
}
}
```
##### 測試程式
```C#
internal static class Program
{
private static void Main(string[] args)
{
var city = new City(){Name = "test1",Age = 100};
var city2 = Clones.CloneObj(city);
city2.Name = "test2";
Console.WriteLine(city);
Console.WriteLine(city2);
Console.ReadKey();
}
}
```
##### 輸出結果
```
test1 is 100
test2 is 100
```
- Client 端若需要使用 Clone, 僅需要使用 Clones.CloneObj<T>(args), 至於如何實作的, Client 並不需要關心.
- 日後需要修改 CloneObj 的實作, 僅需要修改 Clones.CloneObj<T>(args) 即可, 不需要修改每一個需要使用 CloneObj 的 Client 端.
## 總結
- Facade 把複數子系統包起來在一個類別 => 讓外界對其統一呼叫就好
- Facade 能將 Client 端程式碼以及複雜的子系統隔開. 降低呼叫端對複雜子系統的依賴
- Facade 讓使用者更容易使用一個複雜的子系統, 也避免使用者需要去進行複雜的呼叫操作
- Facade 可能會變成 God Object(耦合太多類別)
- Facade 實作上跟 Adapter 很像,但重視的地方不一樣. Facade 重視的是 Client 看到的介面接口.
- Facade 為了已存在的類別定義新的介面/接口/Facade, 反之 Adapter 試著讓已存在的介面/接口能再被利用.
- Adapter 經常只包一個類別在其內部使用, 但 Facade 常常是包整個子系統(複數類別).
- Client 可透過這個接口介面(Facade)去知道有哪些功能可以使用.
- 使用者無須了解哪些子系統被使用到(亙本不需要認識到子系統),, 也無須了解如何正確地使用子系統的相關邏輯, 更不用理解子系統的實作邏輯.
- 子系統不需要認識到 Facade
- Facade 將 Client 與子系統隔開, 因此降低了 Client 需要操作的物件數量以及使子系統更容易使用(將操作物件的邏輯放在Facade).
- Facade 能降低子系統物件之間循環依賴的可能性 ( 子系統不能再使用 Facade ! )
- Facade 無法防止 Client 直接使用子系統
## 參考
[Facade](https://refactoring.guru/design-patterns/facade)
[[Design Pattern] 外觀模式 Facade Pattern](https://dotblogs.com.tw/jesperlai/2018/04/15/153646)
[外观模式](https://www.runoob.com/design-pattern/facade-pattern.html)
[Facade Pattern](https://softwaredevelopmenttricky.blogspot.com/2017/12/facade-pattern.html)
[Facade Design Pattern](https://sourcemaking.com/design_patterns/facade)
[[Design Pattern] Facade 門面模式](https://ithelp.ithome.com.tw/articles/10227186)
[Programming Patterns Overview](https://kremer.cpsc.ucalgary.ca/patterns/)
---
###### Thank you!
You can find me on
- [GitHub](https://github.com/s0920832252)
- [Facebook](https://www.facebook.com/fourtune.chen)
若有謬誤 , 煩請告知 , 新手發帖請多包涵
# :100: :muscle: :tada: :sheep:
<iframe src="https://skilltree.my/c67b0d8a-9b69-47ce-a50d-c3fc60090493/promotion?w=250" width="250" style="border:none"></iframe>