ข้ามไปยังเนื้อหา

Hide Delegate and Remove Middle Man

Hide Delegate ใช้เมื่อ client เอื้อมผ่าน object หนึ่งไปเรียก method บน object ที่สอง — person.department.manager — ให้ object แรกมี method ของตัวเองที่ซ่อนการกระโดดนั้นไว้ client จะได้เรียกแค่ person.manager() และเลิกผูกกับโครงสร้างที่อยู่เบื้องหลัง person

Remove Middle Man คือท่ากลับกัน เมื่อ object หนึ่งแทบไม่ทำอะไรนอกจากส่งต่อการเรียกไปยัง delegate ครั้งแล้วครั้งเล่า ก็ปล่อยให้ client คุยกับ delegate ตรง ๆ แล้วลบ method ที่ส่งต่อทิ้ง

Hide Delegate รักษา Inappropriate Intimacy กับภายในของ class และเป็นหน้าตาภาคปฏิบัติของ Law of Demeter คือคุยกับเพื่อนของคุณ ไม่ใช่เพื่อนของเพื่อน สายยาว ๆ อย่าง a.getB().getC().doIt() มัดให้ caller ผูกกับความสัมพันธ์สองชั้นที่ไม่มีธุระต้องรู้ ส่วน smell ฝั่งตรงข้ามคือ Middle Man คือ class ที่ method เกือบทั้งหมดเขียนว่า “return delegate.something()” ส่งต่อเยอะจนตัวห่อไม่ได้เพิ่มอะไรเลย

Person ที่ผู้จัดการของพวกเขาถูกค้นพบผ่าน Department ของพวกเขา ก่อนหน้านี้ client นำทางผ่านสาย หลังจากนั้น Person ซ่อน delegate ไว้หลัง method manager() (ใช้ Remove Middle Man เพื่อย้อนกลับไปอีกทางเมื่อตัวห่อหยุดคุ้มกับการมีอยู่)

// Before — client walks person.department.manager
class Department {
constructor(public manager: string) {}
}
class Person {
constructor(public name: string, public department: Department) {}
}
const managerName = person.department.manager;
// After — Hide Delegate
class Department {
constructor(private _manager: string) {}
get manager(): string {
return this._manager;
}
}
class Person {
constructor(public name: string, private department: Department) {}
manager(): string {
return this.department.manager;
}
}
const managerName = person.manager();
flowchart LR
  subgraph Before["Before — caller walks the chain"]
    A["client"] --> B["person.department"]
    B --> C[".manager"]
  end
  subgraph After["After — Hide Delegate"]
    D["client"] --> E["person.manager()"]
    E -.->|"internally"| F["department.manager"]
  end
  Before -.->|"Hide Delegate"| After
  After -.->|"Remove Middle Man"| Before
Hide Delegate ห่อสายไว้ ส่วน Remove Middle Man แกะออก

Hide Delegate

  1. สำหรับแต่ละ method บน delegate ที่ client เอื้อมไปเรียก ให้เพิ่ม method ส่งต่อง่าย ๆ บน server (object ที่ client ถืออยู่แล้ว)
  2. อัปเดต client ให้เรียก method server ใหม่แทนการเดินไล่ตามสาย
  3. รัน test หลังแต่ละ method ที่คุณเปลี่ยนทิศทาง
  4. เมื่อไม่มี client ไล่เข้าไปหา delegate ตรง ๆ แล้ว ก็ซ่อน accessor ที่เปิดเผย delegate ได้เลย

Remove Middle Man (สิ่งตรงข้าม)

  1. เพิ่ม accessor บน server ที่คืนค่า delegate เอง
  2. สำหรับแต่ละ method ส่งต่อที่ไม่ได้เพิ่มคุณค่า ให้แก้ caller ให้เรียกตรงผ่าน accessor นั้นแทน
  3. รัน test ขณะที่คุณทำไป
  4. ลบ method ส่งต่อทิ้งเมื่อไม่เหลือ caller แล้ว

ซ่อน delegate เมื่อ client เอื้อมผ่าน object หนึ่งไปหาเพื่อนบ้านของ object นั้น เมื่อคุณอยากมีอิสระเปลี่ยนความสัมพันธ์ที่อยู่เบื้องหลังโดยไม่ทำให้ caller พังทั้งแถบ หรือเมื่อสายเรียกยาวจนอ่านเหมือนคำสั่งนำทาง

ความเสี่ยงคือทำเลยเถิด ซ่อนทุก delegate แล้ว server จะเต็มไปด้วย method ส่งต่อจนตัวเอง กลายเป็น Middle Man เสียเอง ตรงนั้นแหละคือจังหวะหยิบ Remove Middle Man มาใช้ แล้วปล่อยให้ client คุยกับ delegate อีกครั้ง ท่าทั้งสองเป็นปุ่มหมุน หมุนไปทางห่อหุ้มเมื่อโครงสร้างยังผันผวน หมุนไปทางตรงไปตรงมาเมื่อตัวห่อเป็นแค่สัญญาณรบกวน

ใช้ Hide Delegate เมื่อใช้ Remove Middle Man เมื่อ
client รู้เรื่อง internal structure มากเกินไปclass กลายเป็น pass-through ทุกอย่าง ไม่มี logic
ต้องการลด coupling ระหว่าง client กับ delegateการเพิ่ม delegation method ทุก method ของ delegate ไม่คุ้มค่า
การเปลี่ยน delegate ต้องแก้ client หลายจุดclient ที่ต่างกันต้องการ access ที่ต่างกัน

⚠️ รู้จักเวลาที่ต้องสลับทิศทาง:

  • ถ้า class กำลังซ่อน delegate จนกลายเป็น God Object ให้ Remove Middle Man
  • ถ้า client ต้อง traverse chain ยาว (a.b.c.d()) ให้ Hide Delegate
  • ทั้งสองเป็น refactoring ที่ตรงข้ามกัน — ใช้ตามที่ pain จริง ๆ บอก
Hide Delegate ช่วยให้คุณเคารพหลักการใด?
Hide Delegate ทำอะไรให้กับ client?
เมื่อใดที่คุณควรหยิบ Remove Middle Man มาใช้?
อะไรคือความสัมพันธ์ระหว่าง Hide Delegate และ Remove Middle Man?