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.managerclass Department { constructor(public manager: string) {}}
class Person { constructor(public name: string, public department: Department) {}}
const managerName = person.department.manager;
// After — Hide Delegateclass 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();# Before — client walks person.department.managerclass Department: def __init__(self, manager): self.manager = manager
class Person: def __init__(self, name, department): self.name = name self.department = department
manager_name = person.department.manager
# After — Hide Delegateclass Department: def __init__(self, manager): self._manager = manager
@property def manager(self): return self._manager
class Person: def __init__(self, name, department): self.name = name self._department = department
def manager(self): return self._department.manager
manager_name = person.manager()// Before — client walks person.Department.Managertype Department struct { Manager string}
type Person struct { Name string Department Department}
managerName := person.Department.Manager
// After — Hide Delegatetype Department struct { manager string}
func (d Department) Manager() string { return d.manager}
type Person struct { Name string department Department}
func (p Person) Manager() string { return p.department.Manager()}
managerName := person.Manager()// Before — client walks person.department.managerstruct Department { manager: String,}
struct Person { name: String, department: Department,}
let manager_name = &person.department.manager;
// After — Hide Delegatestruct Department { manager: String,}
impl Department { fn manager(&self) -> &str { &self.manager }}
struct Person { name: String, department: Department,}
impl Person { fn manager(&self) -> &str { self.department.manager() }}
let manager_name = 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
- สำหรับแต่ละ method บน delegate ที่ client เอื้อมไปเรียก ให้เพิ่ม method ส่งต่อง่าย ๆ บน server (object ที่ client ถืออยู่แล้ว)
- อัปเดต client ให้เรียก method server ใหม่แทนการเดินไล่ตามสาย
- รัน test หลังแต่ละ method ที่คุณเปลี่ยนทิศทาง
- เมื่อไม่มี client ไล่เข้าไปหา delegate ตรง ๆ แล้ว ก็ซ่อน accessor ที่เปิดเผย delegate ได้เลย
Remove Middle Man (สิ่งตรงข้าม)
- เพิ่ม accessor บน server ที่คืนค่า delegate เอง
- สำหรับแต่ละ method ส่งต่อที่ไม่ได้เพิ่มคุณค่า ให้แก้ caller ให้เรียกตรงผ่าน accessor นั้นแทน
- รัน test ขณะที่คุณทำไป
- ลบ 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 จริง ๆ บอก