# 7. Adapter
###### tags: `DesignPatterns`
## 關於 Adapter 本篇將討論以下幾個問題
> ### 1. 關於 Adapter
> ### 2. UML
> ### 3. 將 UML 轉為程式碼
> ### 4. 情境
---
## 測試環境:
>OS:Windows 10
>IDE:Visual Studio 2019
---
## 1. 關於 Adapter
> Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
>
> by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns)
- 將 Class 的接口再次封裝為呼叫端需要的樣子
- Adapter 使原本接口不相容的 Class 可以協同工作
Adapter(轉接器)屬於結構型(Structural Patterns),當遇到**呼叫端與接收端無法對應時**,經由 Adapter 的轉換,使得兩個接口不同的舊有 class 可以串接使用,而不需改變原本程式邏輯,以達到最小幅度修改的目的,一般而言 Adapter 常用於事後補救的情況。
優點:
- 符合 單一職責原則(Single Responsibility Principle)
- 符合 開閉原則(Open Closed Principle)
缺點:
- 過多的 Adapter 會造成整體程式複雜度提升
---
## 2. UML

Class 間關聯:
- Client 關聯 Target
- Adapter 繼承 Target
- Adapter 關聯 Adapter
Class:
- Client:呼叫端
- Target:依據呼叫端所定義的接口
- Adapter:轉接器實作,將`Adaptee`轉換為呼叫端所需的中間層
- Adaptee:欲呼叫但接口不合無法直接使用的被呼叫端
---
## 3. 將 UML 轉為程式碼
依據呼叫端所定義的接口
```C#
/// <summary>
/// 定義呼叫端使用的接口
/// </summary>
public interface ITarget
{
void Request();
}
```
被呼叫端所提供的接口
```C#
/// <summary>
/// 被呼叫端的接口
/// </summary>
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
```
連接呼叫端與被呼叫端的`Adapter`
```C#
/// <summary>
/// 實作 Adapter
/// </summary>
public class Adapter : ITarget
{
private Adaptee _adaptee = new Adaptee();
public void Request()
{
_adaptee.SpecificRequest();
}
}
```
1. 建立 Adapter `target`
2. 透過`target`呼叫 Adaptee
```C#
static void Main(string[] args)
{
Default.ITarget target = new Default.Adapter();
target.Request();
Console.ReadLine();
}
```
執行結果
```Console
Called SpecificRequest()
```
---
## 4. 情境
我們接到了一個取得倉庫庫存量資訊需要以 JSON 格式回傳的需求
- 原本服務回傳的格式為`List<InStockModel>`
- 呼叫端需要的格式為 JSON
依據呼叫端所定義的接口,這邊預期會取回 sting 格式的 JSON 字串
```C#
/// <summary>
/// 定義呼叫端使用的接口
/// </summary>
public interface IInStock
{
string GetInStockData();
}
```
`InStockService`回傳庫存資料格式為`List<InStockModel>`
```C#
/// <summary>
/// 被呼叫端的接口
/// </summary>
public class InStockService
{
public List<InStockModel> GetInStockData()
{
Console.WriteLine("Called InStockService GetInStockData()");
var inStockData = new List<InStockModel>
{
new InStockModel{Id = 1, Name = "AA", InStock = 100},
new InStockModel{Id = 2, Name = "BB", InStock = 200},
new InStockModel{Id = 3, Name = "CC", InStock = 300},
};
return inStockData;
}
}
/// <summary>
/// 庫存資料格式
/// </summary>
public class InStockModel
{
public int Id { get; set; }
public string Name { get; set; }
public int InStock { get; set; }
}
```
`Adapter`將資料由`List<InStockModel>`轉為 JSON 後回傳
```C#
/// <summary>
/// 實作 Adapter
/// </summary>
public class Adapter : IInStock
{
private InStockService _inStockService = new InStockService();
public string GetInStockData()
{
var inStockData = _inStockService.GetInStockData();
return JsonSerializer.Serialize(inStockData);
}
}
```
1. 建立 Adapter `inStock`
2. 透過`inStock`取得 JSON 格式的 InStockData
```C#
static void Main(string[] args)
{
Situation.IInStock inStock = new Situation.Adapter();
var data = inStock.GetInStockData();
Console.WriteLine(data);
Console.ReadLine();
}
```
執行結果
```Console
Called InStockService GetInStockData()
[{"Id":1,"Name":"AA","InStock":100},{"Id":2,"Name":"BB","InStock":200},{"Id":3,"Name":"CC","InStock":300}]
```
---
## 完整程式碼
GitHub:[Structural_01_Adapter](https://github.com/darionnnnnn/blog/tree/master/Blog/Structural_01_Adapter)
---
## 總結
### 由情境的範例中可以看到在 Adapter 中只做了將原本的 Model 轉換成 JSON,不過實際應用上更常會遇到需要呼叫多個服務來達到符合呼叫端的結果,或是 API 改版時可先藉由 Adapter 來作為過渡時期的解決方案。
---
## 參考資料
1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns)
2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799)
2. [dofactory](https://www.dofactory.com/)
3. [Refactoring.Guru](https://refactoring.guru/)
---
## 新手上路,若有錯誤還請告知,謝謝