Design Pattern 101 — Observer Pattern
Design Pattern กลุ่มต่อไปของ GoF นั้นคือ Behavioral Pattern เริ่มต้นที่ Observer Pattern ใช้สำหรับสังเกตการณ์การเปลี่ยนแปลงของ object ดังนั้นในบทความนี้จะมาทบทวนและทำความเข้าใจ Observer Pattern ว่าใช้แก้ปัญหาอะไรได้บ้าง
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 อัตโนมัติ
ทั้ง Subject
และ Observer
จะถูกกำหนดความสัมพันธ์แบบ one-to-many ซึ่ง Observer
จะขึ้นอยู่กับ Subject
เมื่อสถานะของ Subject
เปลี่ยนแปลง Observer
จะได้รับการแจ้งเตือน แต่ก็ขึ้นอยู่กับรูปแบบของการแจ้งเตื่อน (Notification) ซึ่ง Observer
อาจจะถูกอับเดตด้วยค่าใหม่
สรุป
จากการที่ได้ทบทวนและทำความเข้าใจกับ Observer Pattern เป็น pattern ที่เหมาะสำหรับระบบซอฟต์แวร์ที่มีการเฝ้าระวังหรือตรวจการเปลี่ยนแปลงของ Object โดยที่ไม่ต้องตรวจสอบแบบสำหม่ำเสมอ แต่ถ้า Object นั้นเปลี่ยนแปลงก็จะแจ้งให้ Obejct ที่ลงทะเบียนไว้กับ Object นั้น ซึ่งก็เหมือนเคยตัวอย่างใช้ Head First Design Pattern