Facade
ให้ interface ที่เรียบง่ายกว่าสำหรับระบบย่อยที่ซับซ้อน
Facade ใช้เมื่อคุณต้องการ interface ที่เรียบง่ายกว่า สำหรับระบบย่อยของระบบที่ซับซ้อน โดยสร้าง interface ที่แสดงเฉพาะจุดเชื่อมที่จำเป็นสำหรับส่วนย่อยของระบบ แทนที่จะเปิดระบบซับซ้อนทั้งหมด
ปัญหาที่ Facade แก้คือ: เมื่อระบบเติบโตขึ้น มันมักประกอบด้วยหลาย class ที่ต้องเรียกตามลำดับที่ถูกต้อง, ต้องตั้งค่า configuration ที่ยุ่งยาก, หรือต้องรู้ความสัมพันธ์ภายในของ subsystem นั้นอย่างละเอียด — ถ้าปล่อยให้ client เรียกใช้ subsystem โดยตรงทุกจุด code ของ client จะผูกติด (coupling) กับรายละเอียดภายในของ subsystem จนแก้ไขหรืออัปเกรดยาก
แนวคิดหลักคือสร้าง class “หน้าตึก” (facade) ขึ้นมา class หนึ่ง ที่รู้วิธีประสานงาน subsystem ทั้งหมดแทน client แล้วเปิดเผยเฉพาะ method ระดับสูงไม่กี่ตัวที่ client ต้องการจริง ๆ ส่วนรายละเอียดการเรียงลำดับ การจัดการ error หรือการแปลงข้อมูลระหว่าง subsystem จะถูกซ่อนไว้ในตัว facade เอง
ข้อสังเกตสำคัญ: ระบบย่อยไม่รู้ว่ามี facade อยู่ — มีการอ้างอิงจาก facade ไปยังระบบย่อย แต่ไม่มีจากระบบย่อยกลับมา (การพึ่งพาเป็นทางเดียวเสมอ) facade ยังทำหน้าที่เป็นประตูเดียวได้คล้าย Single Point of Enforcement และช่วยลดการละเมิด Law of Demeter เพราะ client คุยกับ object เดียวแทนที่จะไล่เรียกผ่านห่วงโซ่ของ subsystem
Facade ไม่ได้เพิ่มความสามารถใหม่ให้ subsystem — มันแค่ จัดระเบียบการเข้าถึงสิ่งที่มีอยู่แล้ว ให้สะดวกขึ้น subsystem ยังคงเรียกใช้ได้โดยตรงสำหรับ client ที่ต้องการความยืดหยุ่นเต็มรูปแบบ (facade เป็นทางเลือก ไม่ใช่การปิดกั้น)
โครงสร้าง
หัวข้อที่มีชื่อว่า “โครงสร้าง”classDiagram
class Client {
}
class OrderFacade {
-Inventory inventory
-Payment payment
-Shipping shipping
+PlaceOrder(cart)
}
class Inventory {
+Reserve(cart)
+Release(cart)
}
class Payment {
+Charge(amount)
+Refund(amount)
}
class Shipping {
+Dispatch(cart)
+Track(orderId)
}
Client --> OrderFacade
OrderFacade --> Inventory
OrderFacade --> Payment
OrderFacade --> Shipping
- Client — code ที่ต้องการใช้งาน subsystem แต่ไม่อยากรู้รายละเอียดภายใน
- Facade (
OrderFacade) — class เดียวที่ client เห็น รู้ว่าต้องเรียก subsystem ไหนตามลำดับใด และแปลง request ของ client ให้เป็นการเรียกที่ subsystem เข้าใจ - Subsystem classes (
Inventory,Payment,Shipping) — class ที่ทำงานจริง ซับซ้อน มีสถานะและกฎของตัวเอง แต่ไม่รู้จัก facade เลย จึงยังคงใช้งานแยกได้อิสระโดย client อื่นที่ไม่ผ่าน facade
บางครั้งระบบใหญ่มากอาจมี additional facade หลายตัว แบ่งตาม use case ของ client แต่ละกลุ่ม เพื่อไม่ให้ facade ตัวเดียวบวมจนกลายเป็น god object เอง
การทำงาน
หัวข้อที่มีชื่อว่า “การทำงาน”เมื่อ client เรียก method ของ facade เพียงครั้งเดียว facade จะกระจายงานไปยัง subsystem หลายตัวตามลำดับที่ถูกต้อง จัดการผลลัพธ์ระหว่างทาง แล้วคืนผลลัพธ์เดียวกลับไปให้ client — client ไม่ต้องรู้เลยว่าเบื้องหลังมีการเรียกกี่ขั้นตอน
sequenceDiagram
participant C as Client
participant F as OrderFacade
participant I as Inventory
participant P as Payment
participant S as Shipping
C->>F: PlaceOrder(cart)
F->>I: Reserve(cart)
I-->>F: reserved
F->>P: Charge(cart.Total)
P-->>F: charged
F->>S: Dispatch(cart)
S-->>F: dispatched
F-->>C: OrderResult
จุดสำคัญของลำดับนี้คือ facade เป็นที่เดียวที่รู้ว่า “ต้องจองสต๊อกก่อนตัดเงิน แล้วค่อยส่งของ” — ถ้าวันหนึ่งลำดับต้องเปลี่ยน (เช่น ต้องตรวจ fraud ก่อน charge) จะแก้ไขที่จุดเดียวในตัว facade โดยไม่กระทบ client ที่เรียกใช้อยู่ทั้งหมด และไม่กระทบ subsystem เดิมเลย
ตัวอย่าง code
หัวข้อที่มีชื่อว่า “ตัวอย่าง code”// Subsystem 1: จัดการสต๊อกสินค้า มีกฎภายในของตัวเองpublic class Inventory{ public void Reserve(Cart cart) { foreach (var item in cart.Items) { // ตรวจสอบและล็อกสต๊อกไว้ชั่วคราว Console.WriteLine($"จองสินค้า {item.Sku} จำนวน {item.Quantity}"); } }
public void Release(Cart cart) { Console.WriteLine("คืนสต๊อกที่จองไว้ (ใช้เมื่อ rollback)"); }}
// Subsystem 2: จัดการการชำระเงินpublic class Payment{ public string Charge(decimal amount) { Console.WriteLine($"เรียกเก็บเงินจำนวน {amount:C}"); return "txn-12345"; // transaction id }
public void Refund(string transactionId) { Console.WriteLine($"คืนเงินสำหรับ transaction {transactionId}"); }}
// Subsystem 3: จัดการการจัดส่งpublic class Shipping{ public void Dispatch(Cart cart) { Console.WriteLine("สร้างใบจัดส่งและมอบหมายให้ courier"); }}
// Facade: จุดเชื่อมเดียวที่ client เรียก — ซ่อน 3 subsystem ไว้เบื้องหลังpublic class OrderFacade{ private readonly Inventory _inventory = new(); private readonly Payment _payment = new(); private readonly Shipping _shipping = new();
public OrderResult PlaceOrder(Cart cart) { // ลำดับขั้นตอนที่ถูกต้องถูกซ่อนไว้ใน method เดียวนี้ _inventory.Reserve(cart);
string transactionId; try { transactionId = _payment.Charge(cart.Total); } catch (PaymentException) { // ถ้าชำระเงินไม่สำเร็จ ต้องคืนสต๊อกที่จองไว้ — logic ประสานงานแบบนี้ // คือสิ่งที่ facade มีไว้ซ่อน ไม่ให้ client ต้องรู้ _inventory.Release(cart); throw; }
_shipping.Dispatch(cart);
return new OrderResult(transactionId, "สั่งซื้อสำเร็จ"); }}
// Client เห็นแค่ interface เดียว ไม่ต้องรู้จัก Inventory, Payment, Shipping เลยvar facade = new OrderFacade();var result = facade.PlaceOrder(cart);เมื่อไหร่ควรใช้
หัวข้อที่มีชื่อว่า “เมื่อไหร่ควรใช้”- เมื่อ subsystem มีหลาย class ที่ client ส่วนใหญ่ต้องเรียกร่วมกันตามลำดับที่ตายตัว
- เมื่อต้องการลด coupling ระหว่าง code ของ client กับรายละเอียดภายในของ library หรือ framework ภายนอก เพื่อให้อัปเกรดหรือเปลี่ยน implementation ในอนาคตได้ง่ายขึ้น
- เมื่อกำลังทำ refactor ระบบเดิม (legacy) แบบค่อยเป็นค่อยไป — สร้าง facade ที่มี interface แบบที่ “อยากให้เป็น” ก่อน แล้วค่อย ๆ ย้าย logic เข้าไปทีละส่วนโดยไม่กระทบ client (แนวทางแบบ strangler fig)
- เมื่อต้องการจุดเดียวสำหรับ enforce กติกาหรือ validation ก่อนเข้าถึง subsystem หลายตัว
- เมื่อต้องการให้ทดสอบง่ายขึ้น เพราะทดสอบผ่าน facade จุดเดียวแทนที่จะ mock subsystem หลายตัวในทุกจุดที่เรียกใช้
เมื่อไหร่ไม่ควรใช้
หัวข้อที่มีชื่อว่า “เมื่อไหร่ไม่ควรใช้”- เมื่อ client บางกลุ่มต้องการความยืดหยุ่นหรือ fine-grained control เหนือ subsystem แต่ละตัวจริง ๆ — การบังคับผ่าน facade เดียวจะจำกัดความสามารถของพวกเขาโดยไม่จำเป็น (แก้ได้ด้วยการเปิดให้เข้าถึง subsystem ตรงได้ควบคู่ไปกับ facade)
- เมื่อ subsystem เรียบง่ายอยู่แล้ว การเพิ่ม facade จะเป็นเพียง indirection ที่ไม่ได้ลดความซับซ้อนจริง
- เมื่อพยายามยัดทุก feature ของทุก client เข้าไปใน facade ตัวเดียว จนมันบวมกลายเป็น god object ที่ผูกกับทุกอย่างในระบบ (ควรแยกเป็นหลาย facade ตาม use case แทน)
- เมื่อ facade เริ่มมี business logic ของตัวเองมากขึ้นเรื่อย ๆ แทนที่จะแค่ประสานงาน — นั่นเป็นสัญญาณว่า logic นั้นควรอยู่ใน domain layer ไม่ใช่ใน facade
ข้อดีและข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีและข้อเสีย”| ด้าน | รายละเอียด |
|---|---|
| ข้อดี | ลด coupling ระหว่าง client กับรายละเอียดภายในของ subsystem |
| ข้อดี | ทำให้ code ฝั่ง client อ่านง่ายขึ้น เพราะเรียก method เดียวแทนหลายขั้นตอน |
| ข้อดี | เปลี่ยนหรืออัปเกรด subsystem ภายในได้โดยไม่กระทบ client ตราบใดที่ signature ของ facade ไม่เปลี่ยน |
| ข้อดี | เป็นจุดเริ่มต้นที่ดีสำหรับ refactor ระบบเดิมแบบค่อยเป็นค่อยไป |
| ข้อเสีย | เสี่ยงกลายเป็น god object ที่ผูกกับทุก class ใน application ถ้าไม่ระวังขอบเขต |
| ข้อเสีย | เพิ่มชั้น indirection อีกชั้นหนึ่ง ซึ่งอาจไม่จำเป็นถ้า subsystem เรียบง่ายอยู่แล้ว |
| ข้อเสีย | ถ้า client ต้องการความสามารถที่ facade ไม่ได้เปิดให้ ต้องแก้ facade หรือ bypass มันไปเรียก subsystem ตรง ๆ |
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Adapter — คล้าย Facade ตรงที่ห่อ interface อื่นไว้ แต่ Adapter มีไว้เพื่อแปลง interface ที่เข้ากันไม่ได้ให้ใช้งานร่วมกันได้ ไม่ใช่เพื่อทำให้เรียบง่ายขึ้น
- Bridge — แยก abstraction ออกจาก implementation เพื่อให้ทั้งสองฝั่งเปลี่ยนแปลงอิสระจากกัน ต่างจาก Facade ที่มุ่งลดความซับซ้อนของการเรียกใช้
- Composite — จัดกลุ่ม object เป็นโครงสร้างต้นไม้ที่เรียกใช้ผ่าน interface เดียวกัน มักใช้ร่วมกับ Facade ได้
- Mediator — คล้าย Facade ตรงที่ประสานงานหลาย object แต่ Mediator เน้นความสัมพันธ์แบบสองทางระหว่าง object ที่รู้จัก mediator นั้น ต่างจาก subsystem ของ Facade ที่ไม่รู้จัก facade เลย
- Single Point of Enforcement — Facade เป็นเทคนิคหนึ่งที่ช่วยสร้างประตูทางเดียวสำหรับ enforce กติกาก่อนเข้าถึงระบบย่อย
- Law of Demeter — Facade ช่วยลดการละเมิดกฎนี้ เพราะ client คุยกับ object เดียวแทนที่จะไล่เรียกผ่านห่วงโซ่ของ subsystem