observer something in your heart | https://unsplash.com/photos/kSLNVacFehs

Observer Design Pattern มันยังไงนะ ?

Supawit R
odds.team
Published in
3 min readNov 1, 2020

--

อยากจะมาเล่าถึง Design Pattern ตัวนึงที่น่าสนใจก็คือ Observer Design Pattern มันน่าสนใจยังไง? สำหรับผมมองว่าการเรียนรู้ Pattern ตัวนี้มันน่าสนุกมาก ถ้าเราเข้าใจคอนเซ็ปของมันเราไปต่อยอดอะไรได้อีกเยอะเลย เช่นพวก reactive programming, pub/sub service (เช่น kafka หรือ rabbit mq) ก็มีเรื่องนี้มาเกี่ยวข้อง

เรานำ Pattern นี้มาใช้ตอนไหน ?

จินตนาการดูว่ามี 2 Object คือ ลูกค้า (Customer) และร้านค้า (Store)

สมมติสถานการณ์ที่ 1: มีลูกค้าสนใจในสินค้าบางอย่างที่อยู่ในร้านค้าที่กำลังจะมาวางขายเร็ว ๆ นี้ (อาจจะเป็นโทรศัพท์รุ่นใหม่) ลูกค้าก็อาจจะต้องเขาก็ไปที่ร้านค้าทุกวันว่ามีสินค้าหรือไม่ การที่ลูกค้าต้องมาร้านทุกวัน แล้วพบว่าสินค้าที่สนใจยังไม่มีขาย จะถูกมองว่าเป็นการเดินทางที่ไร้ความหมาย

สมมติสถานการณ์ที่ 2: มองในทางกลับกัน แทนที่ลูกค้าจะต้องมาที่ร้าน ก็เปลี่ยนเป็นร้านค้าเป็นคนติดต่อไปหาลูกค้าด้วยการส่งอีเมลจำนวนมาก (อาจถูกมองว่าเป็นการสแปม) ไปหาลูกค้าทุกคนในทุก ๆ ครั้งที่มีสินค้าใหม่เข้าร้าน สิ่งนี้อาจจะช่วยลูกค้าบางคนที่สนใจสินค้านั้น ๆ ทราบถึงข้อมูลดังกล่าว แต่ในขณะเดียวกันอาจจะทำให้ลูกค้าคนอื่น ๆ อารมณ์เสีย แล้วไม่กลับมาดูอีเมลหรือบล็อคไปเลย เพราะอีเมลเหล่านั้นไม่มีประโยชน์สำหรับพวกเขา

ปัญหา การเดินทางไปร้านที่เสียเวลาและการส่งอีเมลสแปมจากร้านค้า | https://refactoring.guru/design-patterns/observer

จากกรณีที่ยกตัวอย่างน่าจะทำให้พอเห็นภาพของปัญหาได้บ้าง ลองคิดว่าหากเป็นการเขียนโปรแกรมมันก็ดูเป็นการแย่มาก ๆ เลยหากแอปช้อปปิ้งเกิดไม่มีแจ้งเตือนสินค้ามาหาผู้ใช้ ก็จะเป็นผู้ใช้ก็จะเสียเวลามานั่งเปิดเพื่อมาดูว่ามีของในสต็อกหรือยัง ดูเสียเวลาและหงุดหงิดน่าดู หรือร้านค้าส่งอีเมลไปมั่ว (spam) นอกจากลูกค้าคนอื่น ๆ ได้ประสบการณ์ที่ไม่ดีแล้ว ร้านค้ายังเกิดค่าใช้จ่ายเช่น ค่าส่งเมลที่ไร้ค่า

เราจะแก้ปัญหานั้นได้อย่างไร ?

ลองนำ Observer Pattern มาปรับใช้ สิ่งแรกที่ต้องรู้คือเราจะกำหนด 2 สิ่งขึ้นมาเพื่อคุยกันนั่นคือ Publisher และ Subscriber เราจะแทนว่า Publisher คือร้านค้าและ Subscriber คือลูกค้า

note: Publisher/Subscriber หากไปดูที่อื่นอาจเจอคำอื่น เช่น Subject/Observer ให้เข้าใจว่ามันเหมือนกัน

Observer Pattern Diagram | https://refactoring.guru/design-patterns/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)” ซึ่งเป็นทุนที่มอบความรู้ ทักษะและโอกาสดีในการฝึกฝนพัฒนาทักษะที่มีอยู่ให้เฉียบคมมากยิ่งขึ้นครับ ❤️

--

--

Supawit R
odds.team

a developer who love to learn, read, and sleep.