อยากจะมาเล่าถึง Design Pattern ตัวนึงที่น่าสนใจก็คือ Observer Design Pattern มันน่าสนใจยังไง? สำหรับผมมองว่าการเรียนรู้ Pattern ตัวนี้มันน่าสนุกมาก ถ้าเราเข้าใจคอนเซ็ปของมันเราไปต่อยอดอะไรได้อีกเยอะเลย เช่นพวก reactive programming, pub/sub service (เช่น kafka หรือ rabbit mq) ก็มีเรื่องนี้มาเกี่ยวข้อง
เรานำ Pattern นี้มาใช้ตอนไหน ?
จินตนาการดูว่ามี 2 Object คือ ลูกค้า (Customer) และร้านค้า (Store)
สมมติสถานการณ์ที่ 1: มีลูกค้าสนใจในสินค้าบางอย่างที่อยู่ในร้านค้าที่กำลังจะมาวางขายเร็ว ๆ นี้ (อาจจะเป็นโทรศัพท์รุ่นใหม่) ลูกค้าก็อาจจะต้องเขาก็ไปที่ร้านค้าทุกวันว่ามีสินค้าหรือไม่ การที่ลูกค้าต้องมาร้านทุกวัน แล้วพบว่าสินค้าที่สนใจยังไม่มีขาย จะถูกมองว่าเป็นการเดินทางที่ไร้ความหมาย
สมมติสถานการณ์ที่ 2: มองในทางกลับกัน แทนที่ลูกค้าจะต้องมาที่ร้าน ก็เปลี่ยนเป็นร้านค้าเป็นคนติดต่อไปหาลูกค้าด้วยการส่งอีเมลจำนวนมาก (อาจถูกมองว่าเป็นการสแปม) ไปหาลูกค้าทุกคนในทุก ๆ ครั้งที่มีสินค้าใหม่เข้าร้าน สิ่งนี้อาจจะช่วยลูกค้าบางคนที่สนใจสินค้านั้น ๆ ทราบถึงข้อมูลดังกล่าว แต่ในขณะเดียวกันอาจจะทำให้ลูกค้าคนอื่น ๆ อารมณ์เสีย แล้วไม่กลับมาดูอีเมลหรือบล็อคไปเลย เพราะอีเมลเหล่านั้นไม่มีประโยชน์สำหรับพวกเขา
จากกรณีที่ยกตัวอย่างน่าจะทำให้พอเห็นภาพของปัญหาได้บ้าง ลองคิดว่าหากเป็นการเขียนโปรแกรมมันก็ดูเป็นการแย่มาก ๆ เลยหากแอปช้อปปิ้งเกิดไม่มีแจ้งเตือนสินค้ามาหาผู้ใช้ ก็จะเป็นผู้ใช้ก็จะเสียเวลามานั่งเปิดเพื่อมาดูว่ามีของในสต็อกหรือยัง ดูเสียเวลาและหงุดหงิดน่าดู หรือร้านค้าส่งอีเมลไปมั่ว (spam) นอกจากลูกค้าคนอื่น ๆ ได้ประสบการณ์ที่ไม่ดีแล้ว ร้านค้ายังเกิดค่าใช้จ่ายเช่น ค่าส่งเมลที่ไร้ค่า
เราจะแก้ปัญหานั้นได้อย่างไร ?
ลองนำ Observer Pattern มาปรับใช้ สิ่งแรกที่ต้องรู้คือเราจะกำหนด 2 สิ่งขึ้นมาเพื่อคุยกันนั่นคือ Publisher และ Subscriber เราจะแทนว่า Publisher คือร้านค้าและ Subscriber คือลูกค้า
note: Publisher/Subscriber หากไปดูที่อื่นอาจเจอคำอื่น เช่น Subject/Observer ให้เข้าใจว่ามันเหมือนกัน
เรามาดูกันว่าแต่ละฝ่ายทั้ง Publisher และ Subscriber ควรมี operation หรือเมธอดอะไรกันบ้าง
Publisher Operations
- เพื่อให้สามารถลงทะเบียน/ยกเลิกการเป็น subscriber ของ publisher นั้น ๆ ได้ เราจึงจะมีฟังก์ชัน
subscribe
และunsubscribe
- หากมีเหตุการณ์ที่ต้องแจ้งไปหา subscriber เราก็จะมีฟังก์ชัน
notifySubscribers
ขึ้นมา
Subscriber Operations
- หาก Publisher แจ้งเตือนอะไรมาสักอย่าง แล้ว subscriber ควรจะทำอะไรต่อไปก็จะถูกเขียนอยู่ในฟังก์ชัน
update
ไปดูตัวอย่างโค้ดกันดีกว่า
ในที่นี้จะยกตัวอย่างเป็น TypeScript นะ
1. ทำการสร้าง interface Subject (Publisher)
interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(): void;
}
2. ทำการสร้าง interface Observer (Subscriber)
interface Observer {
update(subject: Subject): void;
}
3. สร้าง Concrete ของ Publisher ขึ้นมา
class ConcreteSubject implements Subject {
public state: number;
private observers: Observer[] = [];
public attach(observer: Observer): void {
const isExist = this.observers.includes(observer);
if (isExist) {
return console.log('Subject: Observer has been attached already.');
}
console.log('Subject: Attached an observer.');
this.observers.push(observer);
}
public detach(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer);
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.');
}
this.observers.splice(observerIndex, 1);
console.log('Subject: Detached an observer.');
}
public notify(): void {
console.log('Subject: Notifying observers...');
for (const observer of this.observers) {
observer.update(this);
}
}
public someBusinessLogic(): void {
console.log('\nSubject: I\'m doing something important.');
this.state = Math.floor(Math.random() * (10 + 1));
console.log(`Subject: My state has just changed to: ${this.state}`);
this.notify();
}
}
4. สร้าง Concrete ของ Observer ขึ้นมา
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.');
}
}
}
5. ลองนำมาใช้งาน
const subject = new ConcreteSubject();
const observer1 = new ConcreteObserverA();
subject.attach(observer1);
const observer2 = new ConcreteObserverB();
subject.attach(observer2);
subject.someBusinessLogic();
subject.someBusinessLogic();
subject.detach(observer2);
subject.someBusinessLogic();
ผลลัพธ์ที่จะออกมา
Subject: Attached an observer.
Subject: Attached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 6
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event.
Subject: I'm doing something important.
Subject: My state has just changed to: 1
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
Subject: Detached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 5
Subject: Notifying observers...
หากอ่านมาถึงตรงนี้มีอะไรแล้วคิดว่ามีอะไรผิดพลาด หรือตกหล่นอะไร อยากรบกวนช่วยกันคอมเมนต์เข้ามาได้เลยนะครับ หรือสงสัยอะไรอยากถามก็ได้เช่นกัน เพื่อที่เพื่อน ๆ คนอื่นที่มาอ่านบทความนี้จะได้เนื้อหาที่ถูกต้องและสมบูรณ์ครับ 🙂
อ้างอิง
ขอบคุณภาพและเนื้อหาจาก
https://refactoring.guru/design-patterns/observer
Special Thanks
ขอขอบคุณ “สำนักงานส่งเสริมเศรษฐกิจดิจิทัล (depa)” และคณาจารย์ “คณะเทคโนโลยีสารสนเทศ มจธ. (SIT)” ที่ให้การสนับสนุน “ทุนเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล (KMUTT-depa)” ซึ่งเป็นทุนที่มอบความรู้ ทักษะและโอกาสดีในการฝึกฝนพัฒนาทักษะที่มีอยู่ให้เฉียบคมมากยิ่งขึ้นครับ ❤️