ABAP设计模式之---“观察者模式(Observer Pattern)”

1. 定义

观察者模式也称“发布-订阅模式(Pubish/Subscribe)”, “模型-视图模式(Model/View)”, “源-监听器模式(Source/Linster)”或者“从属者模式(Dependents)”, 它是一种行为型模式。

此种模式定义了一种“一对多”的依赖关系,多个观察者可同时监听某一个主题对象,当主题对象状态改变时,其相关的依赖对象皆得到通知,并自动更新自己。

2. 解读

类比:

  • 这个模式的例子真的太多了。例如,订阅报纸(杂志),这其实是形成了一种订阅者和出版商之间的依赖关系,每当新期刊出版时,出版商便会将最新的期刊邮寄给每一个订阅者。再例如, 股票的股价变动提醒,你可以设定当涨幅或跌幅大于5%时,触发短信通知,这其实也是一种订阅活动间不同对象的依赖关系。
  • 在上述例子中,我们可以看到,发布者和订阅者之间的关系是一对多的,1个发布者可以有N个订阅者, 发布者与订阅者间是存在从属关系的

解析:

  • 在面向对象的程序世界里,观察者模式,其实描述的就是一种对象间的“联动”关系
  • 同时,这种联动关系应该是“松耦合(loose coupling)”的, 当观察者数量改变时,原有的发布者和订阅者应当不受影响,即满足“开放-封闭原则”

类图:
ABAP设计模式之---“观察者模式(Observer Pattern)”_第1张图片
各种角色:

  1. Observable - 抽象通知者(发布者),一个定义了增加、删除、通知等操作观察者对象的接口(或抽象类)
  2. Observer - 抽象观察者,为所有的具体观察者定义一个接口,在得到发布者的通知后,更新自己。抽象观察者的接口通常包含一个Update( )方法,这个方法叫做更新方法
  3. ConcreteObservable - 具体的通知者,它保存了所有观察者对象的引用,当自身状态发生改变时,给所有登记的观察者发出通知
  4. ConcreteObserver - 具体观察者,可保存一个发布者的引用,用于接到更新通知时,获取发布者传递过来的状态,进而执行联动行为

调用关系:
ABAP设计模式之---“观察者模式(Observer Pattern)”_第2张图片

  • 在主框架中实例化“observable发布者”Publisher对象,然后将实例化的具体“观察者/订阅者”注册到“发布者”实例中,当“发布者”状态改变时,其可触发“Observer的Update( )方法”来将状态更新给“观察者”,观察者可通过GetState()方法反向获取发布者的状态,然后基于返回信息,执行相关的行为。
  • 当有新的“观察者”出现时,需要做的仅仅是将“新观察者”的实例注册到“发布者”上即可。原有的代码结构和内容不用做任何调整。

要点:

  • 观察者模式广泛应用于1:N的松耦合场景,也即当一个对象的状态改变时,应同时出发N个相关的实例变化
  • 对于“发布者”与“观察者”之间的状态信息传递有两种方式:Push 或 Pull 。Push方式即“发布者”是信息的中心节点,当更新方法触发时,直接将定义好的数据返回给“观察者”,这种方式也意味着在Update方法中,“发布者”与“观察者”存在更强的耦合关系,因为需要事先协商好传输的数据结构;Pull方式即“发布者”在触发更新方法时,并不会传递具体数据,观察者可根据自己的需求,反向获取“发布者”的状态。Pull方式降低了两者之间的耦合,但在性能上较Push方式稍差,因为其多了一次反向“观察者”对“发布者”的访问
  • 观察者模式有两种常见的实现方式,一种是使用observable接口和observer接口的方式,另一种是使用“事件委托”的方式,这两种方式的具体的实现,可见下一小节的示例代码

设计模式对比:

  1. 经典的MVC模型(Model-View-Controller) 其实就是观察者模式的一个典型应用,在MVC模型中,观察者模式用于“模型Model”和“视图View”两层的解耦,“视图”其实也即是对“模型”的观察者,当模型变化时,相关的视图应该进行联动(例如:数据变化时,相关的图表显示应自动更新)。
  2. 事件机制其实也是观察者模式的体现

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( '<>' ).

  " Delete subscribe for user-b
  lo_book_info->lif_observable~delete_observer( lo_user_b  ).
  lo_book_info->return_book( '<>' ).

运行结果:
ABAP设计模式之---“观察者模式(Observer Pattern)”_第3张图片

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( '<>' ).

运行结果:
ABAP设计模式之---“观察者模式(Observer Pattern)”_第4张图片

以上,是本篇对观察者模式的总结,希望对您有所帮助,欢迎分享、留言。

本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞、转发!

你可能感兴趣的:(设计模式,sap,abap,设计模式,观察者,发布-订阅模式)