Observer
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”Observer นิยามการพึ่งพาแบบหนึ่ง-ต่อ-หลายระหว่าง object เพื่อว่าเมื่อ object หนึ่ง คือ subject เปลี่ยนสถานะ ผู้พึ่งพาทั้งหมดของตัวเองจะได้รับการแจ้งเตือนและอัปเดตโดยอัตโนมัติ subject เก็บรายการของ observer ไว้และกระจายข่าวสารไปยังพวกเขาโดยไม่ต้องรู้ชนิดที่เป็นรูปธรรมของพวกเขา
สมมติว่าจดหมายข่าวเผยแพร่บทความใหม่ และมีหลายสิ่งที่ต้องตอบสนอง ได้แก่ ตัวส่งอีเมล log และฟีดสดบนหน้าแรก วิธีดิบ ๆ คือให้ผู้เผยแพร่เรียกแต่ละตัวโดยตรง แต่เมื่อนั้นผู้เผยแพร่ต้อง import และรู้จักผู้บริโภคทุกตัว และทุกครั้งที่คุณเพิ่มหรือลบผู้บริโภค คุณก็ต้องแก้ผู้เผยแพร่ ผู้เผยแพร่จึงกลายเป็นศูนย์กลางที่เชื่อมติดกับทุกสิ่งที่อยู่ปลายน้ำ
ความผันแปรในที่นี้คือ ใครกำลังฟังอยู่ และกลุ่มนั้นเปลี่ยนแปลงไปตามเวลา Observer พลิกกลับความสัมพันธ์นั้น ผู้บริโภคลงทะเบียนตัวเองกับ subject และ subject กระจายข่าวสารไปยังรายการใด ๆ ที่ถืออยู่ในขณะนั้น ผู้เผยแพร่รู้จักแค่ observer interface เท่านั้น ผู้สมัครรับข่าวจึงเข้ามาและจากไปได้ในขณะรัน และผู้เผยแพร่ก็ไม่เคยเปลี่ยนแปลง
โครงสร้าง
หัวข้อที่มีชื่อว่า “โครงสร้าง”classDiagram
class Subject {
-observers: List~Observer~
+subscribe(o)
+unsubscribe(o)
+notify(news)
}
class Observer {
<<interface>>
+update(news)
}
class EmailSubscriber {
+update(news)
}
class FeedSubscriber {
+update(news)
}
Subject o--> Observer
Observer <|.. EmailSubscriber
Observer <|.. FeedSubscriber - Subject — ถือรายการของ observer และเปิดเผยการ subscribe, unsubscribe และวิธีแจ้งเตือน เมื่อสถานะของตัวเองเปลี่ยน จะกระจายข่าวสารไปยัง observer ทั้งหมด
- Observer — interface ที่ผู้พึ่งพาทุกตัว implement โดยทั่วไปคือ method update เดียวที่รับการเปลี่ยนแปลง
- Concrete Observer — ผู้พึ่งพาหนึ่งตัว ตอบสนองต่อการอัปเดตในแบบของตัวเอง
- Client — ลงทะเบียนและถอดถอน observer และกระตุ้นการเปลี่ยนสถานะบน subject
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”subject จดหมายข่าวที่กระจายแต่ละพาดหัวใหม่ไปยังผู้สมัครรับข่าวที่ลงทะเบียนอยู่ในขณะนั้น แต่ละภาษาใช้กลไก callback หรือ interface ที่เป็นสำนวนเฉพาะของตัวเอง
interface Observer { update(headline: string): void;}
class Newsletter { private observers = new Set<Observer>();
subscribe(o: Observer): void { this.observers.add(o); } unsubscribe(o: Observer): void { this.observers.delete(o); } publish(headline: string): void { for (const o of this.observers) { o.update(headline); } }}
class EmailSubscriber implements Observer { constructor(private readonly who: string) {} update(headline: string): void { console.log(`Email to ${this.who}: ${headline}`); }}
const news = new Newsletter();const alice = new EmailSubscriber('alice');news.subscribe(alice);news.subscribe(new EmailSubscriber('bob'));news.publish('Observer explained'); // both notifiednews.unsubscribe(alice);news.publish('Now without alice'); // only bobfrom typing import Protocol
class Observer(Protocol): def update(self, headline: str) -> None: ...
class Newsletter: def __init__(self) -> None: self._observers: list[Observer] = []
def subscribe(self, o: Observer) -> None: self._observers.append(o)
def unsubscribe(self, o: Observer) -> None: self._observers.remove(o)
def publish(self, headline: str) -> None: for o in list(self._observers): o.update(headline)
class EmailSubscriber: def __init__(self, who: str) -> None: self.who = who
def update(self, headline: str) -> None: print(f"Email to {self.who}: {headline}")
news = Newsletter()alice = EmailSubscriber("alice")news.subscribe(alice)news.subscribe(EmailSubscriber("bob"))news.publish("Observer explained") # both notifiednews.unsubscribe(alice)news.publish("Now without alice") # only bobpackage main
import "fmt"
// Observer is implemented by anything that wants to be notified.type Observer interface { Update(headline string)}
type Newsletter struct { observers []Observer}
func (n *Newsletter) Subscribe(o Observer) { n.observers = append(n.observers, o)}
func (n *Newsletter) Publish(headline string) { for _, o := range n.observers { o.Update(headline) }}
type EmailSubscriber struct{ Who string }
func (e EmailSubscriber) Update(headline string) { fmt.Printf("Email to %s: %s\n", e.Who, headline)}
func main() { news := &Newsletter{} news.Subscribe(EmailSubscriber{Who: "alice"}) news.Subscribe(EmailSubscriber{Who: "bob"}) news.Publish("Observer explained") // both notified}trait Observer { fn update(&self, headline: &str);}
struct Newsletter { observers: Vec<Box<dyn Observer>>,}impl Newsletter { fn new() -> Self { Newsletter { observers: Vec::new() } } fn subscribe(&mut self, o: Box<dyn Observer>) { self.observers.push(o); } fn publish(&self, headline: &str) { for o in &self.observers { o.update(headline); } }}
struct EmailSubscriber { who: String,}impl Observer for EmailSubscriber { fn update(&self, headline: &str) { println!("Email to {}: {}", self.who, headline); }}
fn main() { let mut news = Newsletter::new(); news.subscribe(Box::new(EmailSubscriber { who: "alice".into() })); news.subscribe(Box::new(EmailSubscriber { who: "bob".into() })); news.publish("Observer explained"); // both notified}ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”- ข้อดี: subject และ observer ของตัวเองเชื่อมโยงกันอย่างหลวม subject รู้จักแค่ observer interface เท่านั้น
- ข้อดี: observer สามารถถูกเพิ่มหรือลบในขณะรันได้ รวมถึงแบบไดนามิกขณะที่โปรแกรมทำงาน
- ข้อดี: เป็นกระดูกสันหลังของระบบ event การ binding ของ UI และ reactive data flow
- ข้อเสีย: ลำดับการแจ้งเตือนมักไม่ถูกระบุไว้ ดังนั้น observer ต้องไม่พึ่งพาการทำงานตามลำดับตายตัว
- ข้อเสีย: observer ที่ไม่ถูกถอดถอนทำให้เกิด memory leak และห่วงโซ่การแจ้งเตือนที่ยาวอาจไล่ตามหรือ debug ได้ยาก
pattern ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “pattern ที่เกี่ยวข้อง”- Mediator ก็แยกผู้ส่งออกจากผู้รับเช่นกัน แต่ส่งต่อผ่านศูนย์กลางแทนการกระจายข่าวโดยตรง
- Command มักเป็น payload ที่ observer ได้รับ เปลี่ยนการแจ้งเตือนให้กลายเป็นแอ็กชันที่ execute ได้
| Observer | Mediator | Command | |
|---|---|---|---|
| ทิศทาง | 1 subject → N observer | N peer ↔ 1 mediator | caller → command → receiver |
| coupling | subject ไม่รู้จัก observer | peer ไม่รู้จักกัน | caller ไม่รู้ receiver |
| use case | event / state change notification | chat room, form validation | undo/redo, queue, log |
| ตัวอย่าง | EventEmitter, RxJS, DOM events | Redux store, event bus | button action, HTTP request |