1. 定义
观察者模式也称“发布-订阅模式(Pubish/Subscribe)”, “模型-视图模式(Model/View)”, “源-监听器模式(Source/Linster)”或者“从属者模式(Dependents)”, 它是一种行为型模式。
此种模式定义了一种“一对多”的依赖关系,多个观察者可同时监听某一个主题对象,当主题对象状态改变时,其相关的依赖对象皆得到通知,并自动更新自己。
2. 解读
类比:
- 这个模式的例子真的太多了。例如,订阅报纸(杂志),这其实是形成了一种订阅者和出版商之间的依赖关系,每当新期刊出版时,出版商便会将最新的期刊邮寄给每一个订阅者。再例如, 股票的股价变动提醒,你可以设定当涨幅或跌幅大于5%时,触发短信通知,这其实也是一种订阅活动间不同对象的依赖关系。
- 在上述例子中,我们可以看到,发布者和订阅者之间的关系是一对多的,1个发布者可以有N个订阅者, 发布者与订阅者间是存在从属关系的
解析:
- 在面向对象的程序世界里,观察者模式,其实描述的就是一种对象间的“联动”关系
- 同时,这种联动关系应该是“松耦合(loose coupling)”的, 当观察者数量改变时,原有的发布者和订阅者应当不受影响,即满足“开放-封闭原则”
类图:
各种角色:
- Observable - 抽象通知者(发布者),一个定义了增加、删除、通知等操作观察者对象的接口(或抽象类)
- Observer - 抽象观察者,为所有的具体观察者定义一个接口,在得到发布者的通知后,更新自己。抽象观察者的接口通常包含一个Update( )方法,这个方法叫做更新方法
- ConcreteObservable - 具体的通知者,它保存了所有观察者对象的引用,当自身状态发生改变时,给所有登记的观察者发出通知
- ConcreteObserver - 具体观察者,可保存一个发布者的引用,用于接到更新通知时,获取发布者传递过来的状态,进而执行联动行为
调用关系:
- 在主框架中实例化“observable发布者”Publisher对象,然后将实例化的具体“观察者/订阅者”注册到“发布者”实例中,当“发布者”状态改变时,其可触发“Observer的Update( )方法”来将状态更新给“观察者”,观察者可通过GetState()方法反向获取发布者的状态,然后基于返回信息,执行相关的行为。
- 当有新的“观察者”出现时,需要做的仅仅是将“新观察者”的实例注册到“发布者”上即可。原有的代码结构和内容不用做任何调整。
要点:
- 观察者模式广泛应用于1:N的松耦合场景,也即当一个对象的状态改变时,应同时出发N个相关的实例变化
- 对于“发布者”与“观察者”之间的状态信息传递有两种方式:Push 或 Pull 。Push方式即“发布者”是信息的中心节点,当更新方法触发时,直接将定义好的数据返回给“观察者”,这种方式也意味着在Update方法中,“发布者”与“观察者”存在更强的耦合关系,因为需要事先协商好传输的数据结构;Pull方式即“发布者”在触发更新方法时,并不会传递具体数据,观察者可根据自己的需求,反向获取“发布者”的状态。Pull方式降低了两者之间的耦合,但在性能上较Push方式稍差,因为其多了一次反向“观察者”对“发布者”的访问
- 观察者模式有两种常见的实现方式,一种是使用observable接口和observer接口的方式,另一种是使用“事件委托”的方式,这两种方式的具体的实现,可见下一小节的示例代码
设计模式对比:
- 经典的MVC模型(Model-View-Controller) 其实就是观察者模式的一个典型应用,在MVC模型中,观察者模式用于“模型Model”和“视图View”两层的解耦,“视图”其实也即是对“模型”的观察者,当模型变化时,相关的视图应该进行联动(例如:数据变化时,相关的图表显示应自动更新)。
- 事件机制其实也是观察者模式的体现
3. 示例代码
3.1 接口实现
在此例中,我们使用Pull方式来进行“观察者”和“发布者”间数据的交互。
每当有书籍返还时,将返还书籍的信息更新给所有的User。
REPORT zobserver_pattern.
INTERFACE lif_observer DEFERRED.
CLASS lcl_book_info DEFINITION DEFERRED.
**********************************************************************
" define an interface for publisher
INTERFACE lif_observable.
METHODS register_observer
IMPORTING
io_observer TYPE REF TO lif_observer.
METHODS delete_observer
IMPORTING
io_observer TYPE REF TO lif_observer.
METHODS notify_observer.
ENDINTERFACE.
**********************************************************************
" define an interface for subscriber
INTERFACE lif_observer.
METHODS update.
ENDINTERFACE.
**********************************************************************
" Book class is a publisher class enabled with observable interface
CLASS lcl_book_info DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_observable.
METHODS get_book_status RETURNING VALUE(rv_status) TYPE string.
METHODS return_book IMPORTING iv_book_name TYPE string.
PRIVATE SECTION.
DATA mv_book_status TYPE string.
DATA mt_observers TYPE TABLE OF REF TO lif_observer.
ENDCLASS.
CLASS lcl_book_info IMPLEMENTATION.
METHOD lif_observable~delete_observer.
DELETE mt_observers WHERE table_line = io_observer.
ENDMETHOD.
METHOD lif_observable~register_observer.
APPEND io_observer TO mt_observers.
ENDMETHOD.
METHOD lif_observable~notify_observer.
LOOP AT mt_observers INTO DATA(lo_observer).
lo_observer->update( ).
ENDLOOP.
ENDMETHOD.
METHOD return_book.
" when something happens in publisher, it can decide whether it is a subscriber relevant
" if yes, notify subscriber
CONCATENATE 'Book "' iv_book_name '" is returned."' INTO mv_book_status.
me->lif_observable~notify_observer( ).
ENDMETHOD.
METHOD get_book_status.
rv_status = mv_book_status.
ENDMETHOD.
ENDCLASS.
**********************************************************************
" User class is subscriber class enabled with observer interface
CLASS lcl_library_user DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES lif_observer.
METHODS constructor
IMPORTING
iv_user_name TYPE string
io_book_info TYPE REF TO lcl_book_info.
PROTECTED SECTION.
DATA mv_book_status TYPE string.
PRIVATE SECTION.
DATA mo_book_info TYPE REF TO lcl_book_info.
DATA mv_user_name TYPE string.
METHODS output_info.
ENDCLASS.
CLASS lcl_library_user IMPLEMENTATION.
METHOD constructor.
mv_user_name = iv_user_name.
mo_book_info = io_book_info.
ENDMETHOD.
METHOD output_info.
WRITE: / mv_user_name, ' Know ', mv_book_status.
ENDMETHOD.
" obtain the reference from publisher, then do actions in subscriber
METHOD lif_observer~update.
mv_book_status = mo_book_info->get_book_status( ).
output_info( ).
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
" Declare publisher and register subscriber
DATA(lo_book_info) = NEW lcl_book_info( ).
DATA(lo_user_a) = NEW lcl_library_user( io_book_info = lo_book_info iv_user_name = 'User-A' ).
DATA(lo_user_b) = NEW lcl_library_user( io_book_info = lo_book_info iv_user_name = 'User-B' ).
lo_book_info->lif_observable~register_observer( lo_user_a ).
lo_book_info->lif_observable~register_observer( lo_user_b ).
" Book returned, publisher will notify subscribers
lo_book_info->return_book( '<<Design Pattern>>' ).
" Delete subscribe for user-b
lo_book_info->lif_observable~delete_observer( lo_user_b ).
lo_book_info->return_book( '<<Clean Code>>' ).
运行结果:
3.2 事件实现
相同的应用场景,我们也可使用ABAP OO中的Event实现。
此例中,在lcl_book_info中我们定义的 book_returned 事件, lcl_library_user类则实现对此事件的监听。
REPORT zobserver_pattern2.
CLASS lcl_book_info DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS get_book_status
RETURNING VALUE(rv_status) TYPE string.
METHODS return_book
IMPORTING iv_book_name TYPE string.
EVENTS book_returned.
PRIVATE SECTION.
DATA mv_book_status TYPE string.
ENDCLASS.
CLASS lcl_book_info IMPLEMENTATION.
METHOD get_book_status.
rv_status = mv_book_status.
ENDMETHOD.
METHOD return_book.
CONCATENATE 'Book "' iv_book_name '" is returned."' INTO mv_book_status.
RAISE EVENT book_returned.
ENDMETHOD.
ENDCLASS.
**********************************************************************
CLASS lcl_library_user DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING
iv_user_name TYPE string.
METHODS on_book_returned FOR EVENT book_returned OF lcl_book_info
IMPORTING sender.
PROTECTED SECTION.
DATA mv_book_status TYPE string.
PRIVATE SECTION.
DATA mv_user_name TYPE string.
METHODS output_info.
ENDCLASS.
CLASS lcl_library_user IMPLEMENTATION.
METHOD constructor.
mv_user_name = iv_user_name.
ENDMETHOD.
METHOD output_info.
WRITE: / mv_user_name, ' Know ', mv_book_status.
ENDMETHOD.
METHOD on_book_returned.
mv_book_status = sender->get_book_status( ).
output_info( ).
ENDMETHOD.
ENDCLASS.
**********************************************************************
START-OF-SELECTION.
**********************************************************************
" Declare publisher and register subscriber
DATA(lo_book_info) = NEW lcl_book_info( ).
DATA(lo_user_a) = NEW lcl_library_user( 'User-A' ).
DATA(lo_user_b) = NEW lcl_library_user( 'User-B' ).
SET HANDLER lo_user_a->on_book_returned FOR lo_book_info.
SET HANDLER lo_user_b->on_book_returned FOR lo_book_info.
" Book returned, publisher will notify subscribers
lo_book_info->return_book( '<<Design Pattern>>' ).
运行结果:
以上,是本篇对观察者模式的总结,希望对您有所帮助,欢迎分享、留言。😉
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!