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 รักษา Message Chains (Fowler บทที่ 3) และเป็นหน้าตาภาคปฏิบัติของ Law of Demeter (Lieberherr, Holland และ Riel, OOPSLA 1988) คือคุยกับเพื่อนของคุณ ไม่ใช่เพื่อนของเพื่อน สายยาว ๆ อย่าง 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 อีกครั้ง ท่าทั้งสองเป็นปุ่มหมุน หมุนไปทางห่อหุ้มเมื่อโครงสร้างยังผันผวน หมุนไปทางตรงไปตรงมาเมื่อตัวห่อเป็นแค่สัญญาณรบกวน