7.7 ABAP OOP 观察者模式 Observer Pattern - 摘自 《SAP ABAP面向对象程序设计:原则、模式及实践》

 

7.7 观察者模式 Observer Pattern

1931年,英国政治家丘吉尔(当时还未担任首相)去美国纽约拜访友人,他下出租车后习惯地先向右观察,然后过马路,结果却被左边开来的车撞倒受伤。当人们跑过去看他的情况时,丘吉尔清醒过来并主动承担责任:"这是我的错"。英国是左侧通行,所以他过马路习惯先右看,再看,而美国正好相反。肇事司机因此脱罪,此事经《纽约时报》报道后,在当地成了一个引发热议的新闻。

 

我们过马路,都需要看信号灯或者路上的车辆状况,也就是每一个人都是观察者,需要按既定的规则观察信号状况,然后判断下一步的动作。

观察的结果和相应的动作是预定的,如"红灯停,绿灯行",或者"过马路,左右看"(少数国家除外),由此决定是否可以通行。

当被观察者(交通灯)发生变化时,所有相关的观察者(行人)们都会感知到交通灯状态的变化,从而采取对应的预定的行动。

 

当一个对象状态发生改变时,这个对象相关依赖所有对象都可以得到通知并能够自动更新的行为型模式就是观察者模式。

 

 

模式名称:

观察者模式

 

问题与分析:

问:当一个对象状态改变后,如何给其他相关的对象发送变化的通知?

答:定义对象间的一对多的依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都可以得到通知并被执行相关动作。

 

 

解决方案:

定义一个被观察者,和多个观察者,并将这些观察者联系到相应的被观察者,当被观察者的状态发生改变时,调用方法逐一地通知观察者对象。

 

其类图如下:

如图7-54所示,被观察者A,或者叫目标类:抽象类,是指被观察的对象。一个被观察者可以接受多个观察者的观察,并可以在自己的内表中添加或者删除某个观察者。被观察者也定义了通知方法,用于通知每一个注册过的观察者。

具体被观察者A1,或者叫具体目标类:是指被观察的抽象类的子类,是真正被实例化的类,当状态发生改变时,该对象就向各个观察者发出通知。

接口B,也叫观察者:观察被观察者,一般定义为接口,声明了一个方法update,表明了一旦接收到被观察者状态改变后相应的更新逻辑。

类B1,也叫具体观察者: 实现了观察者接口,并实现了接口中定义的update方法,定义了一旦接收到被观察者状态改变后相应的更新逻辑。

具体观察者有多个实例,被观察者通过添加和删除观察者来管理对应的观察者记录。

 

 

 

效果:

观察者模式将观察者和被观察者解耦,被观察者可以按需要增加或者删除观察者。并可以及时将变化通知给所有相关的观察者。

 

业务实例:

如图7-55所示,当工厂的库存消耗,库存量低于一定量时,会触发"再订货点",通过MRP会产生计划订单和采购申请,触发采购流程。

我们要求设计一个程序,当库存使用下降到一定的值时,触发"再订货点",这时运行我们的库存检查程序,并为多个供应商按配额创建采购申请,并通过email通知相关的采购组织联系人处理业务。

 

 

 

类图如下:

如图7-56所示,包括库存清点抽象类ZCL_INVENTORY_MGMT,

具体采购组ZCL_INVENTORY_MGMT_PL01,

采购组接口ZIF_PURCHASING_GROUP,

采购组 ZCL_PURCHASING_GROUP_PL01。

 

 

 

如图7-57所示,创建接口ZIF_PURCHASING_GROUP,定义接口方法"UPDATE_TASK_STATUS",这是定义采购组的采购能力的接口方法。

 

 

 

 

如图7-58所示,接口还拥有一个属性,为GROUP_ID,表明了采购组的组号。

 

 

如图7-59所示,创建实现接口的类ZCL_PURCHASING_GROUP_PL01,(在Interfaces页面输入接口ZIF_PURCHASING_GROUP名称,并回车),该类代表工厂PL01内的采购组。

 

 

 

如图7-60所示,为ZCL_PURCHASING_GROUP_PL01继承的接口代码UPDATE_TASK_STATUS添加实现代码。

 

如图7-61所示,为方法UPDATE_TASK_STATUS添加实现代码。 代码简单地打印传入参数,然后打印模拟信息代表具体的业务逻辑。

 

 

 

 

如图7-62所示,创建被监听者的父类抽象类ZCL_INVENTORY_MGMT,取消Final标识,表示可以被继承。

 

如图7-63所示,在Types页面,输入表类型TY_TAB_PURCHASING_GROUP,然后点击类型定义按钮(Direct Type Entry),进入类的代码模式定义。

 

 

 

如图7-64所示,进入类的代码模式定义后,按四步法(4.3节)

定义结构体类型TY_PURCHASING_GROUP,

表类型TY_TAB_PURCHASING_GROUP,

定义内表变量作为属性MT_PURCHASING_GROUP

但工作区结构体变量MS_PURCHASING_GROUP不用作为类变量,可以在这里省略。

 

代码如下:

"示例程序7.17

CLASS zcl_inventory_mgmt DEFINITION
PUBLIC
ABSTRACT
CREATE PUBLIC .

PUBLIC SECTION.

TYPES:
BEGIN OF ty_purchasing_group,
group_id TYPE string,
gi_group TYPE REF TO zif_purchasing_group,
END OF ty_purchasing_group.

TYPES:
ty_tab_purchasing_group TYPE STANDARD TABLE OF ty_purchasing_group
WITH KEY group_id.


DATA mt_purchasing_group TYPE ty_tab_purchasing_group.

METHODS attach .
METHODS detach .
METHODS notify_reorder .

  

如图7-65所示,定义完成后,点击"Form-Base"按钮,返回界面方式,可以看到结构体类型TY_ PURCHASING_GROUP和表类型TY_TAB_PURCHASING_GROUP出现在了Types页面。

 

 

如图7-66所示,定义属性内表变量MT_PURCHASING_GROUP,该内表用于记录所有的观察者(采购组)的记录。类型是表类型TY_TAB_PURCHASING_GROUP。

 

 

 

 

如图7-67所示,为类添加方法"ATTACH",用于添加需要观察库存状况的采购组。

 

 

如图7-68所示,方法"ATTACH"的输入参数为II_PURCHASING_GROUP,类型为接口ZIF_PURCHASING_GROUP,实际传入的就是实现接口的采购组对象实例,然后将该对象实例插入对象内表中。

 

代码如下:

"示例程序7.18

METHOD attach.
DATA: ms_purchasing_group TYPE ty_purchasing_group.
IF ii_purchasing_group IS BOUND.
ms_purchasing_group-group_id = ii_purchasing_group->group_id.
ms_purchasing_group-group = ii_purchasing_group.
INSERT ms_purchasing_group INTO TABLE mt_purchasing_group.
ENDIF.

ENDMETHOD.

   

 

如图7-69所示,添加方法"DETACH",该方法用于将不需要再关注库存状况的采购组对象,从自己的内表中删除。

 

 

如图7-70所示,方法"DETACH"的输入参数为IV_GROUP_ID,类型String。

 

 

如图7-71所示,代码实现如下。

 

代码如下,将一个对象记录从对象内表中删除:

"示例程序7.19

METHOD detach.
IF iv_group_id IS NOT INITIAL.
DELETE me->mt_purchasing_group WHERE group_id = iv_group_id.
ENDIF.
ENDMETHOD.

  

如图7-72所示,添加方法NOTIFY_REORDER,用于通知所有观察者采购组,库存有变化,需要采取业务动作。

 

如图7-73所示,参数为IV_STATUS,采标传入的业务状态类型。

 

实现代码如下,顺序读取内表中的每一条观察者对象记录,然后调用每一个观察者记录的通知方法对其进行通知:

"示例程序7.20

METHOD notify_reorder.
DATA: ms_purchasing_group TYPE ty_purchasing_group.
LOOP AT me->mt_purchasing_group INTO ms_purchasing_group.
WRITE: / '
采购组:', ms_purchasing_group-group->group_id.
ms_purchasing_group-group->update_task_status( iv_status ).
ENDLOOP.
ENDMETHOD.

 

如图7-74所示,创建具体库存ZCL_INVENTORY_MGMT_PL01,该方法继承了父类ZCL_INVENTORY_MGMT。

 

 

 

如图7-75所示,重新定义继承自父类ZCL_INVENTORY_MGMT的方法NOTIFY_REORDER。

  

在这里打印信息"PL01 工厂库存管理通知:",来代表工厂PL01的自身特殊业务,然后用关键字"super->"调用父类ZCL_INVENTORY_MGMT定义的逻辑处理通知。

 

"示例程序7.21

METHOD notify_reorder.
write: / 'PL01
工厂库存管理通知: '.
CALL METHOD super->notify_reorder
EXPORTING
iv_status = iv_status.
ENDMETHOD.

  

最后创建客户端测试程序:

 

代码如下:

"示例程序7.22

REPORT zrep_cls_029.

DATA:
exc_ref TYPE REF TO cx_root,
exc_text TYPE string.
DATA:
go_inv_mgmt_pl01 TYPE REF TO zcl_inventory_mgmt_pl01,
go_purch_group_pl01 TYPE REF TO zcl_purchasing_group_pl01,
gv_group_id TYPE string,
gt_group_id TYPE STANDARD TABLE OF string.

TRY.
"
模拟从数据库表中读取采购组信息。
gv_group_id = '1001'.
INSERT gv_group_id INTO TABLE gt_group_id.
gv_group_id = '3023'.
INSERT gv_group_id INTO TABLE gt_group_id.
gv_group_id = '4045'.
INSERT gv_group_id INTO TABLE gt_group_id.
CREATE OBJECT go_inv_mgmt_pl01.
"
循环创建采购组类对象,注册到库存管理被观察者对象中
LOOP AT gt_group_id INTO gv_group_id.
CREATE OBJECT go_purch_group_pl01.
go_purch_group_pl01->zif_purchasing_group~group_id = gv_group_id.
go_inv_mgmt_pl01->attach( go_purch_group_pl01 ).
FREE go_purch_group_pl01.
ENDLOOP.
"
库存发生变化,通知相关的观察者处理
go_inv_mgmt_pl01->notify_reorder( 'Re-order 2017/09/30' ).
SKIP 2. "
打印2个空行

"删除一个观察者
go_inv_mgmt_pl01->detach( '3023' ).
SKIP 2.
"
库存再次发生变化,通知相关的观察者处理
go_inv_mgmt_pl01->notify_reorder( 'Re-order 2017/10/10' ).
CATCH cx_sy_create_object_error INTO exc_ref.
exc_text = exc_ref->get_text( ).
MESSAGE exc_text TYPE 'I'.
ENDTRY.

  

如图7-76所示,代码执行结果如下。

 

 

可见采购组1001,3023,4045被注册为观察者后,被观察者发生变化,通知观察者采取业务行动。当采购组3023被从观察者列表中删除后,再次发出新通知,剩下的两个观察者就继续被通知到。

 

 

以上是完全采用定制化代码来实现的观察者模式,如果我们利用SAP ABAP的Event(事件功能),我们也可以更方便地获得相同的效果。

 

如图7-77所示,下面是采用Event事件的过程,类关系如下:

被监听者ZCL_SUBJECT设定了事件STATUS_CHANGED。

观察者ZCL_OBSERVER,观察并响应被监听者ZCL_SUBJECT的事件。

 

 

 

如图7-78所示,创建被监听者ZCL_SUBJECT,创建Event,事件名称为STATUS_CHANGED。

 

 

如图7-79所示,创建被监听者ZCL_SUBJECT的方法CHANGE_STATUS。

 

如图7-80所示,方法CHANGE_STATUS调用RAISE EVENT来引发事件 STATUS_CHANGED的发生。

 

如图7-81所示,创建观察者ZCL_OBSERVER,添加方法ACTION_ON_SUBJECT_CHANGED,并设定详细信息(Detail View)。

 

 

 

如图7-82所示,在方法ACTION_ON_SUBJECT_CHANGED的"详细信息"内,设定该方法是被观察者ZCL_SUBJECT 的事件STATUS_CHANGED的处理方法,这样当被观察者的事件引发后,会自动调用方法ACTION_ON_SUBJECT_CHANGED作为后续处理。

 

 

如图7-83所示,设定后方法产生Event Handler图标,表明该方法是一个事件的处理者。

 

 

如图7-84所示,为方法ACTION_ON_SUBJECT_CHANGED设定代码,打印信息,表明被观察者采取了行动。

 

 

 

创建测试调用程序,在类设定中,处理方法只是和类挂钩,代码中需要用语句:

SET HANDLER "处理方法"FOR "发生事件的对象实例"来显示地将处理方法和对象实例挂钩。

该语句的意义是将静态注册的事件响应的类的方法,用代码在程序运行时动态地再次确认。好处是避免了一旦在类定义中设定了响应事件的类,对应的响应类的所有实例就都必须要响应该事件。通过该语句,程序可以根据业务要求,动态地设置注册类对象,更加灵活。

 

代码如下:

"示例程序7.23

REPORT zrep_cls_032.

DATA:
go_subject TYPE REF TO zcl_subject,
go_observer1 TYPE REF TO zcl_observer,
go_observer2 TYPE REF TO zcl_observer.
"
创建被观察对象
CREATE OBJECT go_subject.
"
创建观察者1
CREATE OBJECT go_observer1.
"
创建观察者2
CREATE OBJECT go_observer2.

"
设定观察者1的事件句柄指向被观察者
SET HANDLER go_observer1->action_on_subject_changed FOR go_subject.
"
设定观察者2的事件句柄指向被观察者
SET HANDLER go_observer2->action_on_subject_changed FOR go_subject.

"
被观察者状态修改,触发自身事件,然后触发事件处理者(Event handler Method
go_subject->change_status( ).

  

如图7-85所示,代码运行结果如下,被观察者状态改变后,触发了自身的事件的发生。自身事件又触发了对应的观察者的处理方法,使得所有注册的观察者对被观察者的变化产生了反应。

 

posted @ 2018-10-13 11:13  techtalk  阅读(400)  评论(0)    收藏  举报