目录
?8.2字段符号field symbol 在ABAP OOP中的应用
基于类对象的动态创建则是我们推荐使用的,如前面讲过的各种设计模式,都是与自定义数据库表相配合,才能达成低耦合的灵活的架构。一般的方式是将业务类型与对应的类类型存储在自定义的数据库表中,然后在程序运行时动态读取配置,获得类名称,按字符串的形式传入到所创建的代码中,动态创建对象实例。
动态创建对象实例时,需要注意以下几点。
1)确认类的实例化类型,是否能够直接使用CREATE OBJECT或者NEW来创建,还是需要使用类的静态方法或者辅助类的方法来创建。
2)动态创建类对象时传入的类名字符串是大写的,可以使用“TRANSLATE lv_type_name TO UPPERCASE.”来将字符串转换为大写,否则ABAP会找不到该类。
3)动态创建类对象时,要使用SAP提供的TRY CATCH来控制异常,以防止系统崩溃。
动态调用方法
面向对象的字段符号的使用方法。
调用主程序
子例程
结果:
泛型编程(Generic Programming)是编程范式(Programming Paradigm)的一种,用于支持不依赖具体的数据类型的操作,也就是通用的操作。
ABAP中的泛型采用了预定义的通用类型(Generic Type)与字段符号相配合来实现
ABAP也支持内表的泛型,即采用同一段代码可以针对任意类型的内表进行相同的操作。
代码
*& Report Z000809
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000809.
PARAMETERS dbtab TYPE c LENGTH 20.
"定义通用类型
DATA gt_table TYPE REF TO data.
"定义字段类型
FIELD-SYMBOLS <fs_table> TYPE STANDARD TABLE.
"定义字段类型,<struc>代表结构体;<comp>代表结构体的数据列
FIELD-SYMBOLS: <struc> TYPE any,
<comp> TYPE any.
TRY.
"创建内表的引用数据类型
CREATE DATA gt_table TYPE TABLE OF (dbtab).
"将引用的内表赋予字段类型
ASSIGN gt_table->* TO <fs_table>.
"动态查询数据库表 (dbtab),存储于内表类型的字段符号 <fs_table>
SELECT *
FROM (dbtab)
INTO TABLE <fs_table>.
"循环内表 <fs_table>,获取工作区结构体赋予字段类型 <line1>
LOOP AT <fs_table> ASSIGNING FIELD-SYMBOL(<line1>).
"将工作区结构体 <line1>第 2个列的值取出,赋予 <comp>
ASSIGN COMPONENT 3 OF STRUCTURE <line1> TO <comp>.
"打印 <comp>的值
WRITE: / <comp>.
ENDLOOP.
SKIP 2.
" 判断内表 <fs_table>的第 1条记录是否存在
IF line_exists( <fs_table>[ 1 ] ).
""将工作区结构体 <line1>第 6列的值取出,赋予 <comp>
ASSIGN COMPONENT 6 OF STRUCTURE <fs_table>[ 1 ] TO <comp>.
"打印 <comp>的值
WRITE: / <comp>.
ENDIF.
CATCH cx_sy_create_data_error cx_sy_dynamic_osql_error.
ENDTRY.
结果。
可以用SAP提供的类的反射机制来演示泛型在类对象中的应用。
类对象反射机制可以让我们在程序的运行期(Runtime)获得一个类的具体细节,如类的状态、变量名称和值、方法名称和参数等信息。还可以让我们在运行期实例化对象,通过调用get/set方法获取变量的值。
示例程序:
*&---------------------------------------------------------------------*
*& Report Z000810
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000810.
"定义局部类,包含两个方法和两个属性
CLASS zlcl_cl01 DEFINITION.
PUBLIC SECTION.
DATA: mv_string TYPE string VALUE 'String of Class Attribute'.
DATA: mv_date TYPE string VALUE '2017.10.21'.
METHODS: print_string.
METHODS: print_date.
ENDCLASS."定义局部类的方法
CLASS zlcl_cl01 IMPLEMENTATION.
METHOD:print_string.
WRITE:/ 'print_string:', mv_string.
ENDMETHOD.
METHOD:print_date.
WRITE:/ 'print_date:', mv_date.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION. "定义 go_oref_l 为本地类对象
DATA: go_oref_l TYPE REF TO zlcl_cl01.
"定义 go_oref go_obj 为 abap根类对象,创建对象时必须指定类名
DATA: go_oref TYPE REF TO object.
DATA: go_obj TYPE REF TO object.
"定义 sap标准类 cl_abap_classdescr对象
DATA: go_class_desc TYPE REF TO cl_abap_classdescr.
"1. 以下为类的字段符号的应用
"定义任意类型的字段符号,后面不指定类型,相当于 TYPE any
FIELD-SYMBOLS <obj>.
"根据类名创建实例对象
CREATE OBJECT go_oref_l TYPE zlcl_cl01.
"设定go_oref_l的属性值
go_oref_l->mv_string = 'String of Object go_oref_l '.
"将对象go_oref_l赋予字段类型
ASSIGN go_oref_l TO <obj>.
"将字段类型再赋予临时对象 go_obj
go_obj = <obj>.
"对象 go_obj是字段类型在程序运行时指定的类型
"所以编译器在编译阶段是无法知道其具体类型的
"以下注释掉的属性赋值语句是不可编译的
"go_obj->mv_string = 'String of Object go_oref '.
"而对象 go_obj动态访问方法是可行的
CALL METHOD go_obj->('PRINT_STRING').
"根据参数类名动态创建实例对象
CREATE OBJECT go_oref TYPE ('ZLCL_CL01').
"将对象go_oref 赋予字段类型
ASSIGN go_oref TO <obj>.
"将字段类型再赋予临时对象 go_obj
go_obj = <obj>.
"对象 go_obj动态访问方法是可行的
CALL METHOD go_obj->('PRINT_DATE').
SKIP 2.
"2. 以下为类反射的实现
"调用SAP标准类CL_ABAP_CLASSDESCR方法获取类的属性和方法
go_class_desc ?=
cl_abap_classdescr=>describe_by_object_ref( go_oref ).
DATA:
"定义类中的属性内表和结构
gt_attr_desc TYPE abap_attrdescr_tab,
gs_attr_desc TYPE abap_attrdescr,
"定义类中的方法内表和结构
gt_meth_desc TYPE abap_methdescr_tab,
gs_meth_desc TYPE abap_methdescr.
"定义字段符号,类型为任意类型,用于存储类的方法名称
FIELD-SYMBOLS <fs_meth> TYPE any.
"获取类的方法列表到内表中
gt_meth_desc[] = go_class_desc->methods.
"动态打印类中的方法名称
LOOP AT gt_meth_desc INTO gs_meth_desc.
"将类的方法名称赋予字段符号
ASSIGN gs_meth_desc-name TO <fs_meth>.
WRITE: / <fs_meth>.
ENDLOOP.
"定义字段符号,类型为任意类型,用于存储类的属性名称和属性值
FIELD-SYMBOLS <fs_attr> TYPE any.
"获取类的属性名称到内表中
gt_attr_desc[] = go_class_desc->attributes.
"动态访问类中的属性
LOOP AT gt_attr_desc INTO gs_attr_desc.
"将类的属性名称赋予字段符号
ASSIGN gs_attr_desc-name TO <fs_attr>.
WRITE: / <fs_attr>.
"将类的属性值赋予字段符号
ASSIGN go_oref->(gs_attr_desc-name) TO <fs_attr>.
WRITE: <fs_attr>.
ENDLOOP.
SKIP 2.
"动态访问类中的方法
LOOP AT gt_meth_desc INTO gs_meth_desc.
"动态调用类中的方法
CALL METHOD go_oref->(gs_meth_desc-name).
ENDLOOP.
结果:
泛型与类相似,也是一种抽象数据类型,但泛型不是面向对象的概念,而是一种新的、不同于面向对象的编程范式。
1.使用静态设置断点的功能
2.动态设置断点
3.开始调试
4.调试界面
5.调试过程的执行控制
界面中的按钮依次对应了F5、F6、F7、F8功能。Debug时,鼠标右键点击菜单,选择“Go to statement”,跳过中间逻辑,直接运行到指定的语句行。
6.调试过程的条件设定
7.查看修改值
如图8-31所示,进入调试界面后,双击变量名称,既可以在右侧观察值,也可以点击“Change”按钮来设定新的值。
对于内表变量,可以在详细的数据界面中,点击“Services of the Tool”按钮,为内表进行手工的添加、删除和修改等操作。
8.使用扩展程序检查(Extended Program Check)
9.运行时分析
使用运行时分析(Runtime Analysis),运行事务码SAT,输入程序名,执行运行时分析。
10.使用内存监视器(Memory Inspector)
如图8-37所示,进入调试界面后,在Desktop3页面(或者任意一个可新建调试工具窗体的页面,一个页面最多容纳3~4个工具窗体)点击“New Tool”按钮,然后选择工具界面的“Memory Analysis”下的“Memory Analysis”,双击选中。
展示一下基于面向对象的SALV。直接写入如示例程序8.15所示的报表代码,调用SALV的类CL_SALV_TABLE展示SALV表格控件,调用类CL_SALV_FUNCTIONS_LIST展示对应按钮的功能栏。
示例程序 8.15
代码实现:
*&---------------------------------------------------------------------*
*& Report Z000817
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000817.
"定义父类,商品类
CLASS zcl_goods DEFINITION INHERITING FROM object ABSTRACT.
PUBLIC SECTION.
DATA mv_name TYPE string.
DATA mv_price TYPE p DECIMALS 2.
ENDCLASS.
"定义子类,平板电脑类
CLASS zcl_pad DEFINITION INHERITING FROM zcl_goods.
PUBLIC SECTION.
METHODS print_os_desc.
ENDCLASS.
CLASS zcl_pad IMPLEMENTATION.
METHOD print_os_desc.
WRITE: ' Pad OS: Mobile OS'.
ENDMETHOD.
ENDCLASS.
"定义子类,电话类
CLASS zcl_phone DEFINITION INHERITING FROM zcl_goods.
PUBLIC SECTION.
METHODS print_phone_mode.
ENDCLASS.
CLASS zcl_phone IMPLEMENTATION.
METHOD print_phone_mode.
WRITE: ' Phone Mode: 4G'.
ENDMETHOD.
ENDCLASS.
"定义购物车类
CLASS zcl_shop_cart DEFINITION.
PUBLIC SECTION.
TYPES: ty_tab_goods TYPE STANDARD TABLE OF REF TO zcl_goods.
DATA mt_object TYPE ty_tab_goods.
METHODS add_item
IMPORTING
!io_object TYPE REF TO zcl_goods.
METHODS
remove_item
IMPORTING
!iv_index TYPE sy-tabix.
METHODS get_count
EXPORTING
VALUE(ev_count) TYPE i.
METHODS get_price
EXPORTING
VALUE(ev_price) TYPE p.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_shop_cart IMPLEMENTATION.
METHOD add_item.
IF io_object IS BOUND.
INSERT io_object INTO TABLE mt_object.
ENDIF.
ENDMETHOD.
METHOD remove_item.
IF iv_index IS NOT INITIAL.
DELETE mt_object INDEX iv_index.
ENDIF.
ENDMETHOD.
METHOD get_count.
DATA lv_count TYPE i VALUE 0.
DESCRIBE TABLE mt_object LINES lv_count.
ev_count = lv_count.
ENDMETHOD.
METHOD get_price.
DATA lv_price TYPE p.
DATA lv_object TYPE REF TO zcl_goods.
LOOP AT mt_object INTO lv_object.
lv_price = lv_price + lv_object->mv_price.
ENDLOOP.
ev_price = lv_price.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
"定义对象变量
DATA go_cart TYPE REF TO zcl_shop_cart.
DATA go_goods TYPE REF TO zcl_goods.
DATA go_pad TYPE REF TO zcl_pad.
DATA go_phone TYPE REF TO zcl_phone.
DATA l_i TYPE i.
DATA l_p TYPE p DECIMALS 2.
"创建购物车对象
go_cart = NEW zcl_shop_cart( ).
"模拟从采购订单中获取记录,创建对应的对象,
"插入到购物车的商品列表中,此处为自动向上转型
go_pad = NEW zcl_pad( ).
go_pad->mv_name = 'Pad - MS Surface Pro 4'.
go_pad->mv_price = 7000.
go_cart->add_item( go_pad ).
FREE go_pad.
go_pad = NEW zcl_pad( ).
go_pad->mv_name = 'Pad - Apple iPad Air 2'.
go_pad->mv_price = 6000.
go_cart->add_item( go_pad ).
FREE go_pad.
go_phone = NEW zcl_phone( ).
go_phone->mv_name = 'Phone - Apple iPhone 7'.
go_phone->mv_price = 7000.
go_cart->add_item( go_phone ).
"添加一个商品
go_cart->add_item( go_phone ).
"再删除这个商品
go_cart->remove_item( 4 ).
FREE go_phone."调用购物车方法,获取订单中的商品数量和总价
CALL METHOD go_cart->get_count
IMPORTING
ev_count = l_i.
WRITE: / '选取商品数量 ', l_i.
CALL METHOD go_cart->get_price
IMPORTING
ev_price = l_p.
WRITE: / '选取商品总价', l_p.
"获取订单中的每一个商品,
"根据商品的类型强制向下转型,然后才能重新调用子类的方法和特有的属性
WRITE: /,/ ' 商品名称 ', ' 商品价格 ', ' 商品特有信息 '.
LOOP AT go_cart->mt_object INTO go_goods.
"IS INSTANCE OF 操作符在 ABAP 7.5 中可用
IF ( go_goods IS INSTANCE OF zcl_phone ).
TRY.
go_phone ?= go_goods.
WRITE: / go_phone->mv_name , go_phone->mv_price
. go_phone->print_phone_mode( ).
CATCH cx_sy_move_cast_error.
WRITE: / 'phone wrong cast'.
ENDTRY.
ENDIF.
IF ( go_goods IS INSTANCE OF zcl_pad ).
TRY.
go_pad ?= go_goods.
WRITE: / go_pad->mv_name , go_pad->mv_price.
go_pad->print_os_desc( ).
CATCH cx_sy_move_cast_error.
WRITE: / 'pad wrong cast'.
ENDTRY.
ENDIF.
ENDLOOP.
运行结果
对于ABAP程序,持久化一般是保存到数据库表中,ABAP也提供了自动化方式和服务来帮助我们管理和操作持久化对象。
SE11创建表
SE24创建持久类
返回类定义,可以看到,系统将会自动创建属性及存取属性的方法。
se18创建badi? zbadi_test_001?
程序生成 Zif_exbadi_test_001。 创建方法, get_value. 定义输入 iv_input。
对zbadi_test_001的属性进行更改。 选择 multiple use和 filter-depend。 filter type输入 land1, country key.
zif_exbadi_test_001中get_value 方法中的parameter 增加参数 FLT_val传值。
激活badi。
se19创建两个BADI的实现。Zbadi_test_001_impl1和zbadi_test_001_impl2.
filter的值为CN, BE .
分别改写implement中interface页面的get_value代码。分别激活。
Zbadi_test_001_impl1的关键信息:
屏幕中每一个元素都是有用的。?
调用 类 cl_exithandler的get_instance方法。
go_ref调试中的类为Zcl_ex_badi_test_001并不决定是使用哪个implement。
创建的instance: go_ref运行get_value方法。
flt_val的值决定go_ref使用哪个implement。
系统在调用go_ref->get_value 时堆栈中使用的程序和方法。
新的BAdI是基于新的SAP增强架构的,其运行效率要远远高于Classic BAdI。
新BAdI包括了增强容器(Enhancement Spot)、BAdI定义(BAdI Definition)、接口定义(Interface)、增强实现(Enhancement Implementation)、BAdI实现(BAdI Implementa-tion)、实现类(Class)等组成部分。
上述组成部分的关系具体如下。
1)一个增强容器下可以创建多个BAdI定义。
2)每一个BAdI定义都由一个接口和多个增强实现组成。
3)每个增强实现都可以创建多个BAdI实现。
4)每个BAdI实现里都可以创建一个现实类,并对应着过滤条件(Filter)。
下面我们创建一个新BAdI,根据不同的物料类型,BAdI调用不同的实现类,获取并打印不同的生产订单信息,以下是创建新BAdI,即Kernal BAdI的过程。
对于已存的SAP ABAP代码,重构包括如下几种情况。
1)若原有的代码是传统的ABAP代码,则采用面向对象的ABAP代码重构。
2)若原有的代码是面向对象的ABAP代码,则依然采用面向对象的ABAP代码重构。
3)若原有的(传统的或面向对象的)ABAP代码在ECC平台上,则需要迁移到新的技术平台(迁移到HANA平台,或者采用Fiori进行重写)。
对于情况1,原有的传统的ABAP代码,需要采用面向对象的ABAP代码重构,我们可以认为是对同一个业务需求的代码的重新设计与编写(Re-design&Re-develop),因此可以将其看作是一个新的OOA、OOD、ABAP OOP的过程。
对于这种新旧系统间的替换式的重构,我们建议是不修改原有代码,而是完全设计和新编一套面向对象的代码,经过充分测试后再一次性地切换上线,替代原有的旧代码。
对于情况2,已经存在的面向对象的ABAP代码,依然采用面向对象的ABAP代码进行重构,这是本节要介绍的内容。
对于情况3,ECC平台的ABAP代码,迁移到HANA平台,或者采用Fiori重写,可以参考SAP的官方迁移解决方案。
before
after
系统实现方法: