# Adapter Pattern

เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า **ผู้เชื่อมสัมพันธ์** ละกัน ซึ่งมันอยู่ในกลุ่มของ [**🧱 Structural Patterns**](https://www.saladpuk.com/beginner-1/design-patterns/structural) โดยเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อ **เรามีของ 2 อย่างที่ทำงานด้วยกันได้ค่อนข้างยาก แต่เราก็อยากให้มันทำงานด้วยกันได้โดยไม่ทำให้โค้ดเราซับซ้อนเกินไป** พูดแล้วก็ งงๆ ไปดูโจทย์ของเรากันเลยดีกว่าจะได้เข้าใจได้เร็วขึ้น

{% hint style="info" %}
**แนะนำให้อ่าน**\
บทความนี้เป็นส่วนหนึ่งของมหากาพย์ **Design Patterns** ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช [👦 **Design Patterns**](https://saladpuk.gitbook.io/learn/beginner-1/design-patterns)
{% endhint %}

{% hint style="warning" %}
**หมายเหตุ**\
เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยภาพจากเกม Ragnarok เป็นการอธิบาย ซึ่งหลายๆอย่างนั้นมโนขึ้นมาเพื่อความสนุก และทำให้เข้าใจเนื้อหาได้ง่าย  *Gravity อย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล* 😭
{% endhint %}

## 🧐 โจทย์

สมมุติว่าเรารับงานเว็บทายผลบอลทุกคู่ทั่วโลกเพื่อความบันเทิงมา (ไร้ซึ่งการพนันใดๆทั้งสิ้น 🤐) โดยเมื่อบอลแข่งเสร็จ ตัวเว็บของเราก็ต้องไปดึงผลการแข่งขันจาก Web API ชั้นนำ 2 แห่งนั่นคือ **LongOnGoal, LifeScore** ตามรูปด้านล่าง

![](/files/-MLDF3WL2mKZla5o1Evn)

> 🤔 ทำไมต้องดึงมาจากหลายเว็บเหรอ? ก็**บางเว็บมีผลการแข่งเฉพาะคู่เล็กๆ บางเว็บมีผลเฉพาะคู่ใหญ่ๆ** และบางทีเราก็อาจจะต้อง**ตรวจสอบว่าผลมันตรงกันหรือเปล่า** เพื่อที่จะได้จ่าย... เอ้ย ให้แต้มคนที่ทายถูกได้นั่นเอง 😅 (ส่วนแต้มต่อก็ ... ช่างมันเถอะ 🤐)&#x20;

เท่าที่อ่านๆมาก็เหมือนจะง่ายๆเนอะ แค่ไปดึง API จากเว็บ 2 ตัวมาก็จบแล้วชิมิ ไหนลองไปดู **API สำหรับ ดึงผลคะแนน** ของเขาดูดิ๊

![](/files/-MLDG6EKmtXdTaPj8DIo)

![](/files/-MLDG9PWzU6hVnjVPPH_)

🤯 บรึ๊ม!! แม้ว่าจะเป็นเว็บผลคะแนนบอลเหมือนกันแต่ **API เรียกใช้ไม่เหมือนกัน, parameters ต่างกัน, models ต่างกัน** แล้วแบบนี้เราจะเขียนโค้ดยังไงดีเนี่ย? 😵

## 🧒 แก้โจทย์ครั้งที่ 1

อย่าไปคิดเยอะดิ ก็เขียนๆมันไปเลยจะยากอะไร อย่างแรกเลยก็เขียนไปดึงข้อมูลกับ LongOnGoalAPI มาเก็บไว้ แล้วก็ดึงจาก LifeScoreAPI มา สุดท้ายก็เอา 2 ตัวนี้มาเทียบกันก็จบละนิ ป๊ะโถ่วววว

![](/files/-MLDJF0fgY8hQ1O25Qk6)

```csharp
var result1 = longOnGoalAPI.GetMatchResult(1234);
var result2 = lifeScoreAPI.GetHistory(DateTime.Now, "ManU", "Liver");
```

🤔 ก็เหมือนจะง่ายนะ แต่เราลืมอะไรไปป่ะว่า **model ที่ได้กลับมามันไม่เหมือนกัน** แล้วจะเปรียบเทียบยังไงอ่ะ?

![](/files/-MLDK-syL8e4qx2nh35S)

😎 งั้นก็ขอเขียนโค้ดแปลง model ทั้ง 2 ตัวไว้ตรงนี้เลยละกัน จะได้เทียบกันได้ง่ายๆ ตามด้านล่าง

```csharp
var homeScoreFromResult1 = // หาคะแนนของทีมเหย้า จาก LongOnGoalAPI
var awayScoreFromResult1 = // หาคะแนนของทีมเยือน จาก LongOnGoalAPI

var homeScoreFromResult2 = // หาคะแนนของทีมเหย้า จาก LifeScoreAPI
var awayScoreFromResult2 = // หาคะแนนของทีมเยือน จาก LifeScoreAPI
```

จากโค้ดด้านบนก็ไม่ได้ทำอะไรผิดนะ แก้โจทย์ที่ของเราได้ตามปรกติเลย แต่มันจะเกิดอะไรขึ้นถ้า **เราเพิ่ม Web API ตัวที่ 3, 4, 5 ... เข้ามาล่ะ?** เราก็ต้องไปไล่แก้โค้ดใหม่ทุกครั้งอะดิ แถมยิ่งแก้ยิ่งพันกันเป็นสปาเก็ตตี้ขึ้นไปเรื่อย 😨

เพราะโค้ดของเรามันมีหลายอย่างที่ไม่ตรงหลักในการออกแบบที่ดี เช่น **SRP**, **OCP, DIP** ยังไงล่ะ

{% hint style="danger" %}
**Single-Responsibility Principle (SRP)**\
การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย [**Single-Responsibility Principle**](https://saladpuk.gitbook.io/learn/basic/solid/srp)
{% endhint %}

{% hint style="danger" %}
**Open & Close Principle (OCP)**\
การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้ [**Open & Close Principle**](https://saladpuk.gitbook.io/learn/basic/solid/ocp)
{% endhint %}

{% hint style="danger" %}
**Dependency-Inversion Principle (DIP)**\
การละเมิดกฎข้อนี้จะทำให้ module หลักต้องถูกแก้ไขบ่อยๆ เมื่อตัวที่ทำงานตัวเล็กตัวน้อยมีการเปลี่ยนแปลง แม้จะเปลี่ยนเพียงแค่เล็กน้อยก็ตาม [**Dependency-Inversion Principle**](https://www.saladpuk.com/basic/solid/dip)
{% endhint %}

🥴 อาวล่ะแม้ว่ามันจะมีปัญหาในอนาคตก็ตาม แต่อย่างน้อยเราก็ได้เห็นตัวอย่างในการบ้าพลัง เห็นโจทย์แล้วเมากาวกระโดดลงโค้ดเลย ผลก็คือ Design แบบกาวๆเหล่านั้นจะมีปัญหาในอนาคตนั่นเอง งั้นเดี๋ยวลองวางสมอง แล้วเติมน้ำหอมกันหน่อย เพื่อวิเคราะห์ปัญหาแล้วหาทางแก้ไขกันต่อ

## 🧒 แก้โจทย์ครั้งที่ 2

### 🔥 วิเคราะห์ปัญหา

โจทย์ในรอบนี้ไม่ได้ซับซ้อนอะไรเลย แต่ความยากของมันกลับไปตกอยู่ที่ **งานที่ต้องเชื่อมกับคนอื่น** หรือตัว Web API ต่างๆเพราะ **มันเป็นสิ่งที่เราควบคุมไม่ได้ว่าเขาจะออกแบบมายังไง หรือ เขาจะเปลี่ยนแปลงแก้ไขเมื่อไหร่**

{% hint style="danger" %}
🐲 **ควบคุมไม่ได้**\
เมื่อมีของที่เราควบคุมไม่ได้อยู่ในระบบของเราความบรรลัยก็จะบังเกิด เพราะ **วันดีคืนดีอยู่ๆของพวกนั้นเกิดใช้งานไม่ได้ หรือพฤติกรรมมันเปลี่ยนขึ้นมา ต่อให้โค้ดฝั่งเราเขียนดีแค่ไหนก็ตามก็ย่อมมีผลกระทบตามมา** หลายคนอาจจะคิดว่าเป็นของไกลตัวโปรเจคเราไม่มีของพวกนั้นหรอก งั้นแมวน้ำถามกลับว่า โปรเจคเราได้ใช้ Library ของคนอื่นป่ะ? ซึ่งของพวกนั้นดูเหมือนจะไม่มีพิษมีภัยอะไร แต่จริงๆมันก็คือของที่เราควบคุมไม่ได้แบบหนึ่งเหมือนกัน ลองศึกษา Case Study ด้านล่างต่อละกันถ้าสนใจ
{% endhint %}

{% hint style="info" %}
**Case Study**\
มี Library หลายตัวที่แมวน้ำใช้มาหลายปีก็ไม่มีปัญหาอะไร เช่น [**SimpleInjector**](https://simpleinjector.org/)**,** [**SendGrid**](https://sendgrid.com/) แต่พอมาถึงวันนึงที่เราต้องอัพเกรดเวอร์ชั่นปุ๊ป สิ่งที่เกิดขึ้นกับ **SimpleInjector** นั้นดูเหมือนไม่มีอะไรโค้ด compile ได้ไม่มีปัญหาแต่พฤติกรรมของมันเปลี่ยนไป ทำให้ life-cycle ของแอพผิดปรกติ นั่งหากันยกใหญ่ ส่วนเจ้า **SendGrid** ก็แสบไม่แพ้กัน พี่แกเล่นลบ core interface หลักทิ้ง แล้วไปขึ้น core interface ใหม่โดยไม่เหลืออันเดิมให้ใช้เลย (ปรกติเขาจะไม่ลบทิ้ง แต่จะใส่ obsolete attribute ไว้) ทำให้เราต้องมานั่งเสียเวลา re-implement ใหม่ หรือ Library บางตัวออกอัพเดทใหม่ก็มี bugs แถมมาด้วย\
\
😒 แต่ทั้งหมดจะไปโทษเขาก็ไม่ได้หรอก เราในฐานะ Developer ต้องตรวจของที่จะเอามาใช้งานใน production ก่อนเสมอแหละ (อย่าให้ผู้ใช้มาเป็นเทสเตอร์ให้เราเลย 😂)
{% endhint %}

### 🔥 แก้ไขปัญหา

**เมื่อเรามีของที่เราควบคุมไม่ได้ เราต้องเปลี่ยนเป็นสิ่งที่เราควบคุมได้ก่อน** เพราะไม่อย่างนั้นสนุกแน่ ซึ่งการที่เราจะไปควบคุมของอื่นๆมีหลายวิธีเลย ซึ่งวิธีง่ายสุดคือการทำ **Wrapper Class** โดยเราจะสร้างคลาสขึ้นมาครอบของที่ควบคุมไม่ได้เอาไว้ แล้วเราก็ทำงานกับคลาสตัวนั้นแทนที่จะทำงานกับของที่ควบคุมไม่ได้นั่นเอง ตามรูปด้านล่าง

> Design Pattern ที่มีลักษณะเป็น Wrapper Class มีหลายตัวเลย เช่น [**Proxy**](https://www.saladpuk.com/beginner-1/design-patterns/structural/proxy-pattern), Decorator ลองไปศึกษาต่อได้

![](/files/-MLG6JUG2xB2dJdGvOip)

โดยปรกติ **Wrapper Class จะมีหน้าที่เพียงแค่เรื่องเดียวคือควบคุมสิ่งที่มันดูแลอยู่** ดังนั้นเรื่องการจัดการ Web API ทั้ง 2 ตัวนั้น เราก็จะมี Wrapper Class เอาไว้ดูแลมันโดยเฉพาะเลย ตามรูปด้านล่าง

![](/files/-MLIILfX4SMh4irARo_1)

ถัดมาเรื่องการใช้ Web API ทั้ง 2 ตัว แม้ว่ามันจะต่างกันคนละโลกแค่ไหนก็ตาม แต่ถ้าลองคิดดูดีๆเราก็จะพบว่า ของที่เราอยากได้จากมันจริงๆนั้นคือคะแนนการแข่งขันระหว่าง 2 ทีม ดังนั้น**จริงๆเราไม่ต้องสนใจด้วยซ้ำว่า API พวกนั้นจะส่ง model หน้าตาแบบไหนกลับมา ขอแค่เราสามารถแปลง model พวกนั้นให้กลายเป็นของที่เราสนใจได้ก็พอ** ตามรูปด้านล่าง

![](/files/-MLIN6abrI_TDJ4mMmAD)

ส่วนเรื่องที่ API ทั้ง 2 ตัวมันมีวิธีเรียกใช้ไม่เหมือนกัน ซึ่งถ้าคิดดูดีๆก็จะพบว่า **หน้าที่ในการดูแล API มันไปตกอยู่กับพวก Wrapper ดังนั้นหน้าที่ในการจัดการความซับซ้อนของ API เลยเป็นหน้าที่ของ Wrapper จึงทำให้เราเลือกได้เลยว่าเราอยากให้ Wrapper มีหน้าตาเป็นยังไง** นั่นเองตามรูป&#x20;

![](/files/-MLIS7QNxK4Kzdl-GR9G)

พอมันกลายเป็นแบบรูปด้านบนปุ๊ป เราก็จะพบว่า Wrapper ของเราไม่มีความต่างละ ดังนั้นเราก็สามารถจัดการมันในรูปแบบของ interface ได้เลย ตามรูปด้านล่าง

![](/files/-MLIV1pkx2OSXjipPVuP)

🤠 จากที่ว่ามาทั้งหมดปัญหาเรื่อง เวลาของที่เราควบคุมไม่ได้มีการเปลี่ยนแปลง มันก็จะกระทบแค่ Wrapper ที่ดูแลสิ่งนั้นอยู่เท่านั้น เพราะตัว Wrapper รับผิดชอบการดูแลไปแล้วนั่นเอง ซึ่งก็จะตรงกับกฎของ [**Single-Responsibility Principle**](https://saladpuk.gitbook.io/learn/basic/solid/srp) เรียบร้อย

![](/files/-MLIX5I55jANDlgAfFkY)

🤠 หรือเราจะไปดึงข้อมูลจาก Web API อื่นๆ โค้ดเดิมก็ไม่มีผลกระทบอะไรเลย เพราะเราก็แค่เพิ่ม Adapter ตัวใหม่เข้าไป ซึ่งตรงกับกฎของ [**Open & Close Principle**](https://saladpuk.gitbook.io/learn/basic/solid/ocp) เรียบร้อย

![](/files/-MLIYLuQa1nRKlv91hdP)

🤠 และสุดท้ายต่อให้เราจะเลิกใช้ Web API ตัวไหนไป หรือเรามีการแก้ไข Web API นิดๆหน่อยก็จะไม่มีผลกระทบกับ module หลักที่เรียกใช้ Adapter เหล่านี้แล้ว ซึ่งก็ตรงกับกฎ [**Dependency-Inversion Principle**](https://www.saladpuk.com/basic/solid/dip) เช่นกัลล์

![](/files/-MLIZA7_SsjFyk8POMhn)

ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า **Adapter Pattern** เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

## 🤔 Adapter Pattern คือไย ?

มันคือ **แนวคิดในการแก้ปัญหาตอนที่เรามีของ 2 อย่างที่ทำงานร่วมกันไม่ได้หรือทำได้ยาก แต่เราก็อยากให้มันทำงานด้วยกันได้โดยไม่ทำให้โค้ดเราซับซ้อนเกินไป** ซึ่งในโลกความเป็นจริงเราก็จะเห็น Adapter Pattern ได้จากที่ชาร์จโทรศัพท์ไง เพราะเครื่องบางยี่ห้อจะรับเฉพาะ **Lightning** บางยี่ห้อรับเฉพาะ **Micro USB** บางอันเป็น **USB-C** ดังนั้นหน้าที่ของ Adapter Pattern ก็คือการแปลง ของที่เข้ากันไม่ได้ ให้ลงรอยกันได้นั่นเอง ตามรูปด้านล่าง

![](/files/-MLIi99_jcSFmNpG0l-7)

ดังนั้นเมื่อไหร่ก็ตามที่เราเจอปัญหาเข้ากันไม่ได้ ให้รู้ได้เลยว่า Adapter Pattern อาจจะเป็นหนึ่งในทางออกของเราก็ได้

### 💡 หลักในการคิด

เมื่อไหร่ก็ตามที่เราเจอของ 2 อย่างที่มีการทำงานแตกต่างกัน เราก็แค่สร้างมาตรฐานใหม่ที่จะเอามากำกับดูแลของพวกนั้น ให้มันทำงานตรงตามสิ่งที่เราอยากได้ก็พอ ซึ่งในตัวอย่างของเราได้สร้างมาตรฐานใหม่ที่ชื่อว่า IFootballAdapter เอาไว้กำกับดูแลการทำงานของ Web API ทั้งหลายนั่นเอง

{% hint style="info" %}
**หมายเหตุ**\
โดยปรกติตัวมาตรฐานใหม่ที่เราสร้างขึ้นมาเราจะเรียกมันว่า **Adapter** ส่วนตัวที่ถูก adapter ดูแลอยู่ เราจะเรียกมันว่า **Adaptee**
{% endhint %}

🤠 การนำ Adapter ไปใช้งานนั้น สามารถออกแบบได้เยอะมาก ไม่ได้จำกัดว่าจะต้องเหมือกับในตัวอย่าง เช่นการทำ **Class Adapter Pattern**, **Object Adapter Pattern**

![Object Adapter Pattern (Wikipedia)](/files/-MLIccO3mfBlx9st4fFu)

![Class adapter Pattern (Wikipedia)](/files/-MLIdnPqkFMbj0_5KPLY)

{% hint style="success" %}
**เกร็ดความรู้**\
Class Adapter Pattern นั้นถูกออกแบบมาสำหรับภาษาที่รองรับการทำ multi-inheritance ก็จริง แต่ก็ไม่ได้หมายความว่าภาษาที่ไม่รองรับ multi-inheritance จะทำไม่ได้นะ เพราะเราสามารถทำ implement interface ได้หลายตัว
{% endhint %}

### 😎 Perfect Adapter

เมื่อเราสามารถแปลงข้อมูลจากฝั่ง A ไปหาฝั่ง B ได้ ในทฤษฎีของ Adapter Pattern นั้นก็ยังบอกอีกว่า เราก็ควรทำให้เจ้า Adapter สามารถแปลงข้อมูลกลับจากฝั่ง B ไปหาฝั่ง A ได้ด้วยเช่นกัน

![ส่งข้อมูลไปกลับได้ทั้ง 2 ฝั่ง](/files/-MLIgKKrqHPGvE573lup)

## 🎯 บทสรุป

### 👍 ข้อดี

การนำ **Adapter Pattern** มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง เพราะเราไม่ต้องไปวุ่นวายกับ Concrete class ที่อาจเปลี่ยนแปลงภายหลังได้ แถมจะเพิ่มจะลบ Adapter ตัวไหนก็สามารถทำได้เลยอีกด้วย

### 👎 ข้อเสีย

**เพิ่มความซับซ้อนโดยไม่จำเป็น** เพราะการนำ Adapter ไปใช้ จะทำให้เราไม่สามารถทำงานกับ Source ได้ตรงๆ

{% hint style="danger" %}
**ข้อควรระวัง**\
**อย่านำ Adapter Pattern ไปใช้มั่วซั่ว** เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะเรียกใช้จาก Source ได้ตรงๆ เราจะต้องทำผ่าน Adapter อีกทีหนึ่ง ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด
{% endhint %}

{% hint style="success" %}
เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย [**Mr.Saladpuk**](https://www.facebook.com/mr.saladpuk) และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺
{% endhint %}

![ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘](/files/-MKNmp9HEqzREDsXrZ0H)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.saladpuk.com/beginner-1/design-patterns/structural/adapter-pattern.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
