设计模式 #2:观察者模式

Posted on 2016-7-12 in Code

概括

观察者模式 = 发布者(publisher) + 观察者(observer / subscriber)

模式定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

例子:气象监测应用

建立下一代 Internet 气象观察站。该气象站必须建立在 WeatheData 对象上,由 WeatherData 对象负责追踪目前的天气状况(温度、湿度、气压)。 对问题进行分析,发现我们要做的是: 建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。 当 WeatherObject 对象获得最新的测量数据时,三种布告板必须实时更新。 * 系统必须可扩展,让其他开发人员可以建立定制的布告板。

解决方案1

subject.py

class Subject(object):
    '''Subject interface class'''
    def register_observer(self, observer):
        raise NotImplementedError('abstract Subject')

    def remove_observer(self, observer):
        raise NotImplementedError('abstract Subject')

    def notify_observers(self):
        raise NotImplementedError('abstract Subject')

weather_data.py

from subject import Subject


class WeatherData(Subject):
    def __init__(self):
        self.observers = []
        self.temperature = 0.0
        self.humidity = 0.0
        self.pressure = 0.0

    def register_observer(self, observer):
        '''If need to register observer, just add it to observers list
        '''
        self.observers.append(observer)

    def remove_observer(self, observer):
        '''The same, if observer wants to unregister, we will remove
        him from observer list.'''
        if observer in self.observers:
            self.observers.remove(observer)

    def notify_observers(self):
        '''We sent statement to every observers. Since all observers
         implement update() method, we know how to notify them.'''
        for observer in self.observers:
            observer.update(self.temperature, self.humidity, self.pressure)

    def measurements_changes(self):
        '''We need to notify observers when we get measurements from
        Weather-O-Rama company.'''
        self.notify_observers()

    def set_measurements(self, temperature, humidity, pressure):
        '''We will use this method to test billboard.'''
        self.temperature = temperature
        self.humidity = humidity
        self.pressure = pressure
        self.measurements_changes()

    # Other methods in WeatherData

observer.py

class Observer(object):
    '''Observer interface class'''
    def update(self, temperature, humidity, pressure):
        raise NotImplementedError('abstract Observer')

display_element.py

from weather_data import WeatherData
from observer import Observer

class DisplayElement(object):
    '''DisplayElement interface class'''
    def display(self):
        raise NotImplementedError('abstract DisplayElement')


class CurrentConditionDisplay(Observer, DisplayElement):
    '''Billboard class'''
    def __init__(self, weather_data):
        self.weather_data = weather_data
        weather_data.register_observer(self)

    def update(self, temperature, humidity, pressure):
        self.temperature = temperature
        self.humidity = humidity
        self.display()

    def display(self):
        print "Current conditions: %.1fF degrees and %.1f%% humidity" % (
            self.temperature, self.humidity
        )

解决方案2

对于解决方案1而言,存在两个问题:

  • 当主题对象更新的时候,所有的观察者对象都必须更新,对于观察者而言,可能并不想在执行一些重要的任务的时候,收到此类更新。
  • 对于主题对象发送给观察者对象的数据,并不能满足所有观察者的需求,对于有些观察者而言,可能只需要一点点的数据。

所以,我们需要:

  1. 主题对象可以提供 getter 方法让观察者对象主动拉取数据。
  2. 增加 set_changed() 方法来标记主题对象状态已经改变的事实,增加 notify_observers() 方法来通知观察者。
    1. 先调用 set_changed() 标记状态已经改变的事实。
    2. 调用 notify_observers() 通知观察者。
from subject import Subject


class WeatherData(Subject):
    def __init__(self):
        self.observers = []
        self.temperature = 0.0
        self.humidity = 0.0
        self.pressure = 0.0

    def register_observer(self, observer):
        '''If need to register observer, just add it to observers list
        '''
        self.observers.append(observer)

    def remove_observer(self, observer):
        '''The same, if observer wants to unregister, we will remove
        him from observer list.'''
        if observer in self.observers:
            self.observers.remove(observer)

    def measurements_changed(self):
        self.set_changed()
        self.notify_observers()  # We do not pass data, means use pull mode

    def set_changed(self):
        self.changed = True

    def notify_observers(self):
        '''We sent statement to every observers. Since all observers
         implement update() method, we know how to notify them.'''
        if self.changed:
            for observer in self.observers:
                observer.update()
            self.changed = False

    def measurements_changes(self):
        '''We need to notify observers when we get measurements from
        Weather-O-Rama company.'''
        self.notify_observers()

    def set_measurements(self, temperature, humidity, pressure):
        '''We will use this method to test billboard.'''
        self.temperature = temperature
        self.humidity = humidity
        self.pressure = pressure
        self.measurements_changed()

    # Below three are examples of getter methods
    def get_temperature(self):
        return self.temperature

    def get_humidity(self):
        return self.humidity

    def get_pressure(self):
        return self.pressure

    # Other methods in WeatherData