Design Pattern 101 — Observer Pattern

Phayao Boonon
3 min readAug 26, 2019

--

Photo by Jad Limcaco on Unsplash

Design Pattern กลุ่มต่อไปของ GoF นั้นคือ Behavioral Pattern เริ่มต้นที่ Observer Pattern ใช้สำหรับสังเกตการณ์การเปลี่ยนแปลงของ object ดังนั้นในบทความนี้จะมาทบทวนและทำความเข้าใจ Observer Pattern ว่าใช้แก้ปัญหาอะไรได้บ้าง

https://refactoring.guru/design-patterns/observer

Observer Pattern

Problem

ถ้าเราต้องพัฒนาระบบสังเกตการณ์สภาพอากาศ (Weather Monitoring) จากสถานีตรวจวัดสภาพอากาศ (Weather Station) ที่จะวัดค่าของสภาพอากาศจากอุปกรณ์เซนเซอร์วัดค่า ความชื่น (Humidity) อุณหภูมิ (Temperature) และความดัน (Pressure) ซึ่งมี WeatherData เป็น object เพื่อนำค่าที่วัดได้ไปแสดงผลบนอุปกรณ์แสดงผล

public class WeatherData implements Subject {
// field

public WeatherData(CurrentConditionDisplay conditionDisplay,
StatisticsDisplay statisticsDisplay,
ForecastDisplay forecastDisplay) {
this.currentConditionDisplay = conditionDisplay;
this.statisticsDisplay = statisticsDisplay;
this.forecastDisplay = forecastDisplay;
}
// getter public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();

currentConditionDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}

ใน classWeatherData มี method getTemperature() getHumidity() และ getPressure() ที่จะไปอ่านค่าจากเซนเซอร และ method measurementsChanged() จะเรียกใช้ พร้อมทั้ง update ค่าได้อ่านได้ ให้กับอุปกรณ์แสดงผลต่างๆ CurrentConditionDisplay StatisticsDisplay และ ForecastDisplay เพื่อแสดงผลลัพย์ในรูปแบบที่ต่างกัน ที่ inject มาใน constructor

จะเห็นได้ว่าถ้าเราต้องการเพิ่ม หรือเปลี่ยนแปลงอุปกรณ์แสดงผล เราจะต้อง inject อุปกรณ์แสดงผลใหม่ และเพิ่มให้ udpate อุปกรณ์ใหม่ใน measurementsChanged() เพราะขึ้นอยู่กับ concrete implementation และขัดต่อหลักการ Open/Closed ของ SOLID principle

Solution

ดังนั้นถึงเวลาของ Observer Pattern ที่จะมาแก้ไขปัญหานี้ ที่เราต้องการ update หลายๆ object พร้อมๆ กันได้ โดยสร้าง interface Observer ที่มี method update() โดยมี parameter เป็น temp, humidity และ pressure

public interface Observer {
void update(float temp, float humidity, float pressure);
}

และ interface DisplayElement เพื่อเป็นส่วนของความสามาถด้านการแสดงผล

public interface DisplayElement {
void display();
}

โดยให้อุปกรณ์แสดงผลต่างๆ implement interface นี้ ทำให้อุปกรณ์แสดงผลมีคุณสมบัติเดียวกันคือ Observer และ DisplayElement

public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temp;
private float humidity;
private float pressure;

@Override
public void update(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}

@Override
public void display() {
System.out.println("Current conditions: " + temp + "C degree and " + humidity + "% humidity");
}
}

สร้าง interface Subject เพื่อเป็นส่วนสำหรับ เพิ่ม/ลบ/แจ้งเตือน อุปกรณ์แสดงผลที่เป็น Observer

public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}

และให้ WeatherData implement interface นี้

public class WeatherData implements Subject {
private float temp;
private float humidity;
private float pressure;

List<Observer> observers;

public WeatherData() {
observers = new ArrayList<>();
}

@Override
public void registerObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
observers.stream()
.filter(observer -> observer == o).findFirst()
.ifPresent(observer -> observers.remove(observer));
}

@Override
public void notifyObservers() {
observers.forEach(observer -> {
observer.update(temp, humidity, pressure);
});
}

public void setMeasurement(float temperature, float humidity, float pressure) {
this.temp = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}

public void measurementsChanged() {
notifyObservers();
}
}

จะเห็นได้ว่า เราสามาถเพิ่มหรือลบอุปกรณ์แสดงผลที่เป็น Observer และใช้ method notifyObservers() เพื่อแจ้ง Observer ทั้งหมดที่ลงทะเบียนไว้ว่ามีการเปลี่ยนแปลง โดยแทน code ใน method measurementsChanged() ทั้งหมด

เพิ่ม method setMeasurement() เพื่อเซตค่าที่วัดได้จากเซ็นเซอร์ต่างๆ และเรียกใช้ measurementsChanged เพื่อแจ้งเตือน Observer ทั้งหมดด้วย

และจะเห็นว่าเราไม่ได้ inject อุปกรณ์แสดงผลเข้ามาใน constructor และเรียก method update() ทำให้เราไม่ขึ้นอยู่กับ concreate implementation ของอุปกรณ์แสดงผล

ใน Observer class ให้เพิ่ม constructor โดย pass Subject เข้ามาใน constructor

public class CurrentConditionDisplay implements Observer, DisplayElement {
// exist field
private Subject weatherData;

public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
this.weatherData.registerObserver(this);
}

// exist method
}

ทำให้ใน WeatherStation เราต้องสร้าง WeatherData ก่อนแล้ว pass เข้าไปใน constructor ของอุปกรณ์แสดงผลแล่ละตัวๆ

public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();

CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

weatherData.setMeasurement(25, 65, 30.4f);
weatherData.setMeasurement(32, 80, 29.1f);
weatherData.setMeasurement(34, 86, 25.1f);
}
}

Observer Pattern เป็นการกำหนด one-to-many dependency หระว่าง object ดังนั้น dependency ทั้งหมดจะถูกแจ้งเตื่อนและ update อัตโนมัติ

Head First Design Pattern, หน้า 51

ทั้ง Subject และ Observer จะถูกกำหนดความสัมพันธ์แบบ one-to-many ซึ่ง Observer จะขึ้นอยู่กับ Subject เมื่อสถานะของ Subject เปลี่ยนแปลง Observer จะได้รับการแจ้งเตือน แต่ก็ขึ้นอยู่กับรูปแบบของการแจ้งเตื่อน (Notification) ซึ่ง Observer อาจจะถูกอับเดตด้วยค่าใหม่

Head First Design Pattern, หน้า 52

สรุป

จากการที่ได้ทบทวนและทำความเข้าใจกับ Observer Pattern เป็น pattern ที่เหมาะสำหรับระบบซอฟต์แวร์ที่มีการเฝ้าระวังหรือตรวจการเปลี่ยนแปลงของ Object โดยที่ไม่ต้องตรวจสอบแบบสำหม่ำเสมอ แต่ถ้า Object นั้นเปลี่ยนแปลงก็จะแจ้งให้ Obejct ที่ลงทะเบียนไว้กับ Object นั้น ซึ่งก็เหมือนเคยตัวอย่างใช้ Head First Design Pattern

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

No responses yet