PA教材提纲 TAW12-1

Unit1 Introduction to Object-Oriented Programming(面向对象编程介绍)

1.1 Explaining the Object-Oriented Programming Model(解释面向对象编程)

编程语言发展史:

  1. 最早的语言是面向过程语言( procedural programming ): COBOL
  2. 差不多同时出现了面向对象( object-oriented programming )和面向逻辑与过程( logical and procedural programming )的语言
    1.  
      1. object-oriented programming: Simulation67
      2. logical and procedural programming: C, Pascal

面向过程语言( Procedural Programming Model )的特点:

  • Separation of data and functions
  • Usually non-encapsulated access to data
  • Possibility of encapsulating functions using modularization

面向过程语言( Procedural Programming Model )的组成:

  • Type definitions
  • Data declarations
  • Main program ( Calling subroutines, function modules )
  • Definition of subroutines

Encapsulation of Data Using Function Groups:

  • main program can't access the global data of a function group, but can call the function modules
  • function modules can access the components, particularly the global data of their own function group
  • BAPIs ( Business Application Programming Interface ) are implemented as function modules
  • Business Objects are implemented as function groups

Multiple Instantiation in Object-Oriented Programming(多重实例化的概念):the possibility to create several runtime instances using the same program context(即可以通过一个类实现多个对象)

用函数组封装和用类封装的区别:

  1. encapsulation using function groups:
    1. You use modularization units to encapsulate functions and data
    2. You can work with the global data of the main program
  2. encapsulation using objects:
  3.  
    1. You can use objects to encapsulate functions and data
    2. You can create multiple instances ( objects )

ABAP OO的特点:

  • ABAP Objects statements can be used in procedural ABAP programs
  • Objects ( classes ) contain procedural ABAP statements
  • Only object-oriented concepts that have proved useful(ABAP只采用了其它面向对象语言被证明有用的概念)
  • Increased use of type checks
  • Obsolete statements are prohibited(废弃的语句被禁止)

Client/Server Relationship Between Objects(对象交互类似于客户/服务器模式):

  • The client object must adhere(依附) to the protocol of the server object
  • The protocol of server object must be clear enough for a potential client to follow it without any problems
  • Objects can perform both roles simultaneously
  • The services are distributed among objects to avoid redundancy through delegation

Object-Oriented Programming的重要概念:

  • Inheritance继承,即子类可继承父类的属性和方法
  • Polymorphism多态,即接口可以有多种实现方式
  • Event Control事件控制,即除了message之外,对象之间还能用触发event的方式交互
  • Abstraction抽象,即对真实世界复杂关系的简化

面向对象语言( Object-Oriented Programming )的特点:

  • Objects are a directly abstraction of the real world
  • Objects are units made up of data and functions
  • Processes can be implemented in a better way than in procedural programming

面向对象语言( Object-Oriented Programming )的优点:

  • Improved software structure and consistency in the development process一致性
  • Reduced maintenance and less susceptibility to error低敏感性
  • Better integration of the customer or user into the analysis, design, and maintenance process集成性
  • Simpler and more secure extension of the software扩展性

1.2 Analyzing and Designing with Unified Modeling Language (UML) (使用UML分析与设计)

类( Class )的概念:

  • A class is a description of a quantity of objects that exhibit the same characteristics and the same behavior
  • Classes are Abstraction Forms, instances are used to explain the concept of abstraction

类( Classes )和对象( Objects )的对比:

  1. Class:
    1. General description of objects
    2. Specifies status data ( attributes ) and behavior ( methods )
  2. Objects:
    1.  
      1. Representation of part of the real world
      2. Concrete from(具象)/specimen(样品)/instance of a class

UML概念:

  • UML is a globally standardized modeling language, for specification, construction, visualization, and documentation of software system models
  • UML is an industry standard and was developed by the Object management Group in September 1997

(更多参考资料请查看http://www.omg.org)

UML图类:

  1. Class diagrams(类图): They show the relationship between classes. This is a static view of a model.
  2. Behavior diagrams(行为图): They show the sequence in which the objects relate to each other.
  3. Component diagrams(组件图): They show the organization and dependencies of components.
  4. Object diagrams(对象图): It's a snapshot taken during program execution, describes instances of classes and the relationships between them. ( It's not a new type of diagram, but rather a variant of the class diagram. 即对象图是类图的一部分。)
  5. Sequence diagrams(时序图): They show certain processes or situations, describes the sequence in which objects are processed and the interaction they have with each other.(注意: 时序图展示了对象的生命周期lifeline

类图( Class diagrams )的组成:

  1. Class类
    1. Name of the class类名
    2. Attributes of the class ( optional )
    3. Methods of the class ( optional )
  2. Association关联
    1.  
      1. Represent an association between class symbols by drawing a line between them
      2. Specify the cardinality ( referred to as multiplicity ) of the relationship at the end of each line
      3. You must use arrows to indicate the navigation options
      4. You must write the association name in italics above the line, which may contain an arrow to indicate the read direction
  3. Generalization and Specialization通用性与特性(即父类和子类)

类图关联( association )有两种特别的类型:

  • 聚合( aggregation ): Aggregation is a special case of association: a whole part relationship(聚合代表了整体和部分的关系,比如车和车轮)
  • 组成( composition ): Composition is a special case of aggregation: an existence-dependent whole-part relationship(组成的部分必须依赖整体存在而存在,即有先后顺序,比如租赁公司和租赁合同)

时序图( sequence diagrams )展示的动作有:

  • When objects are created or deleted
  • When objects exchange messages

时序图( sequence diagrams )展示的状态有:

  • An object is active when actions are executed
  • An object is indirectly active if it is waiting for a subordinate procedure to end

The Delegation Principle委托原则:即类与类层层调用,不可省略中间的类

Unit2 Fundamental Object-Oriented Syntax(面向对象基本语法)

2.1 Creating Local Classes(创建本地类)

Class的定义:

  • A class is a set of objects that have the same structure and exhibit the same behavior
  • A class provides a blueprint for all objects based on this class
  • A class is made up of components, such as attributes, methods, events, constants, types, and implemented interfaces, defined in the definition part. You can only implement only methods in the implementation part.
  • A class statement cannot be nested; that is, you cannot define a class within a class, however, you can define local auxiliary(辅助) classes for global classes.

Attributes的类型:

  • Elementary data object
  • Structured
  • Table-type

Key Characteristics of Methods方法的关键特性:

  • Methods are internal procedures in classes that determine the behavior of the objects. They can access all the attributes in their class and can therefore change the state of attributes
  • Methods have a signature ( interface parameters and exceptions ) that enables them to receive values when they are called and pass values back to the calling program
  • Methods can have any number of IMPORTING, EXPORTING, and CHANGING parameters. All parameters can be passed by value or reference.

ZTEST26代码(注意: 因为教材中的代码零散而且不全,故以ZTEST26为例,然后按教材的知识点进行评点)

*&-------------------------
*& Report  ZTEST26
*&-------------------------
*& 接口、类与继承的示例
*&-------------------------
REPORT ztest26.

INTERFACE if_shitter.
  METHODS:
    shit.
  DATA:
    introduction TYPE string READ-ONLY. "禁止实例化后修改
  CLASS-DATA:
    in_place TYPE string. "可以用静态属性,但不能给初始值
  CONSTANTS:
    in_time TYPE string VALUE 'at night'. "可以用常量且给初始值
ENDINTERFACE.

CLASS lcl_animal DEFINITION.

PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          iv_name TYPE string,
      shout.

CLASS-METHODS:
      eat.

PROTECTED SECTION.
    DATA:
      mv_name TYPE string.

ENDCLASS.

CLASS lcl_animal IMPLEMENTATION.

METHOD constructor.
    mv_name = iv_name.
  ENDMETHOD.

METHOD shout.
    WRITE:
      / 'Animal ', mv_name,' shout like wawawawa!'.
  ENDMETHOD.

METHOD eat.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_cat DEFINITION INHERITING FROM lcl_animal.

PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          iv_name TYPE string,
      shout REDEFINITION,
      eat_mouse.

INTERFACES if_shitter.
    ALIASES poop FOR if_shitter~shit.

ENDCLASS.

CLASS lcl_cat IMPLEMENTATION.

METHOD constructor.
    super->constructor(
      EXPORTING
        iv_name = iv_name
    ).
  ENDMETHOD.

METHOD shout.
    WRITE:
      / 'Cat ', mv_name, ' shouts like meowmeowmeow!'.
  ENDMETHOD.

METHOD eat_mouse.
    WRITE:
      / 'Cat ', mv_name, ' eats mouse!'.
  ENDMETHOD.

METHOD if_shitter~shit.
    WRITE:
      / 'Cat ', mv_name, ' shits poop!'.
  ENDMETHOD.

ENDCLASS.

CLASS lcl_dog DEFINITION INHERITING FROM lcl_animal.

PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          iv_name TYPE string,
      shout REDEFINITION,
      wave_tail.

ENDCLASS.

CLASS lcl_dog IMPLEMENTATION.

METHOD constructor.
    super->constructor(
      EXPORTING
        iv_name = iv_name
    ).
  ENDMETHOD.

METHOD shout.
    WRITE:
      / 'Dog ', mv_name, ' shouts like aowaowaow!'.
  ENDMETHOD.

METHOD wave_tail.
    WRITE:
      / 'Dog ', mv_name, ' waves tail!'.
  ENDMETHOD.

ENDCLASS.

DATA:
  in_animal TYPE REF TO lcl_animal,
  gt_animal TYPE TABLE OF REF TO lcl_animal,
  gs_animal LIKE LINE OF gt_animal,
  shitter   TYPE REF TO if_shitter,
  in_cat    TYPE REF TO lcl_cat,
  in_dog    TYPE REF TO lcl_dog,
  in_cat1   TYPE REF TO lcl_cat,
  in_dog1   TYPE REF TO lcl_dog.

DATA:
  error     TYPE REF TO cx_sy_move_cast_error.

START-OF-SELECTION.
  CREATE OBJECT in_cat
    EXPORTING
      iv_name = 'Mimi'.

APPEND in_cat TO gt_animal. "父类可以直接接收子类对象

CREATE OBJECT in_dog
    EXPORTING
      iv_name = 'Jack'.

APPEND in_dog TO gt_animal.

LOOP AT gt_animal INTO gs_animal.

in_animal = gs_animal.
    CALL METHOD in_animal->shout.

TRY.

in_cat1 ?= in_animal.

CATCH cx_sy_move_cast_error INTO error.

ENDTRY.

IF error IS INITIAL.
      CALL METHOD in_cat1->eat_mouse.
      "IF in_cat1 IS INSTANCE OF if_shitter.
        "CALL METHOD in_cat1->poop.
      "ENDIF.
    ELSE.
      FREE error.
    ENDIF.

TRY.

in_dog1 ?= in_animal.

CATCH cx_sy_move_cast_error INTO error.

ENDTRY.

IF error IS INITIAL.
      CALL METHOD in_dog1->wave_tail.
      "IF in_dog1 IS INSTANCE OF if_shitter.
        "CALL METHOD in_dog1->poop.
      "ENDIF.
    ELSE.
      FREE error.
    ENDIF.

ENDLOOP.

1. 定义与实现

CLASS <name> DEFINITION. ... ENDCLASS.即类定义

CLASS <name> IMPLEMENTATION. ... ENDCLASS.即类实现

2. 属性定义

DATA: 普通属性

CLASS-DATA: 静态属性

CONSTANTS: 常量属性

TYPES: 类型属性

(READ-ONLY指外部能访问但是不能修改)

3. 属性可见性Visibility of Attributes

PUBLIC SECTION.:父类、子类、外部均可访问

PROTECTED SECTION.: 父类、子类可访问,外部不能访问

PRIVATE SECTION.: 父类可访问,子类、外部不能访问

(如果要调试私有变量,则需要借助友元函数)

Class Interface: 公有属性( public components )之和被称为类接口

若子类继承了父类的方法,但是希望重写( overwrite )旧方法,就需要在定义部分使用REDEFINITION关键字。

4. 静态属性( static attributes )与实例属性( instance attributes )

CLASS-DATA: 静态属性

DATA: 实例属性

CLASS-METHODS: 静态方法

METHODS: 实例方法

(注意: 定义部分方法的关键字为METHODS、CLASS-METHODS,但是实现部分的关键字都是METHOD)

普通的属性和方法,在类实例化后开辟内存并存值,它们在堆内存区,需要new和free关键字用于开辟或释放内存(老版ABAP使用CREATE OBJECT开辟内存)。

静态的属性和方法,在程序加载时就开辟内存并存值,它们在静态内存区(常量也在该内存区)。所以它们不需要类实例化,且具有唯一性(即同一类的实例公用一套静态属性)。

ABAP中也将静态属性称为class attributes,它们主要用于定义: (1) Types and constants (2) Central application data buffers (3) Administrative information, such as instance counter

注意:类内部调用静态方法可以省略类名,而调用实例方法需借助me指针。

5. Methods Signatures方法签名:

在定义部分,方法签名如下

METHODS <method_name>

[ IMPORTING iv_par TYPE <type_name>

EXPORTING ev_par TYPE <type_name>

CHANGING cv_par TYPE <type_name>

RETURNING value( rv_par ) TYPE <type_name>

EXCEPTIONS exception

RAISING exception_class ].

IMPORTING: 输入参数

EXPORTING: 输出参数

CHANGING: 可修改参数

RETURNING: 返回值,最多一个,且不能和EXPORTING和CHANGING同时使用

EXCEPTIONS: 普通异常,不能与异常类并存

RAISING: 异常类,不能与普通异常并存

Functional Methods(函数方法): 只含有一个RETURNING参数,以及若干IMPORTING、EXCEPTION参数的方法。(这种方法最大的用途是隐式调用。)

我们可以在IMPORTING和CHANGING参数后面使用OPTIONAL和DEFAULT关键字,把它们做成可选且有默认值的输入参数。

如果我们定义了EXCEPTIONS参数,那么就能像Function Modules那样配合sy-subrc获取运行结果状态。

如果我们定义了RAISING参数,那么不能使用sy-subrc,而应该使用TRY. ... CATCH. ... ENDTRY.语句。

6. 方法可见性Visibility of Methods(可参考属性可见性)

In a class, declaration of attribute names, method names, event names, constant names, type names, and alias names share the same namespace. 一个类的方法和属性共用命名空间,故它们不能重名,只有重写( overwrite )的方法可以重名。

7. 静态方法( static methods )与实例方法( instance methods )(可参考静态属性与实例属性)

8. UML跟Visibility的关系

+表示公有的

#表示保护的

-表示私有的

_表示静态的

9. 类的内部调用静态方法可以省略类名

2.2 Creating Objects(创建对象)

类的实例化代码示例:

DATA: go_vehicle1 TYPE REF TO lcl_vehicle,

go_vehicle2 LIKE go_vehicle1,

go_vehicle3 LIKE go_vehicle1.

CREATE OBJECT go_vehicle1.

go_vehicle2 = go_vehicle1.

CREATE OBJECT go_vehicle3.

IF go_vehicle1 IS INITIAL.

...

ENDIF.

IF go_vehicle1 IS BOUND.

...

ENDIF.

1. Reference variable: 引用,用TYPE REF TO定义,未实例化前没有指向内存区

2. CREATE OBJECT: 定义对象的语句,它会开辟新的内存区;与之形成对比的是,=会让两边的引用指向同一个内存区。

3. Garbage Collector: 程序运行时自动运行的例程,会将没有引用指向的内存区回收。

4. IS INITIAL: 没有实例化(也可用IS NOT BOUND)

IS BOUND: 已经实例化(也可用IS NOT INITIAL)

(BOUND关键字为只对object reference起作用。)

5. object reference和data reference:

我们平时类的引用就是object reference,其实普通数据可以使用data reference,其写法如下:

CREATE DATA gv TYPE string.

gv->* = 'AB'.

多实例( multiple instances )代码示例:

(1) 实例表:

DATA: go_vehicle TYPE REF TO lcl_vehicle,

gt_vehicle TYPE TABLE OF REF TO lcl_vehicle.

CREATE OBJECT go_vehicle.

APPEND go_vehicle TO gt_vehicles.

CREATE OBJECT go_vehivle.

APPEND go_vehicle TO gt_vehicles.

(2) 聚合( aggregation ):

DATA: go_vehicle TYPE REF TO lcl_vehicle,

go_wheel TYPE REF TO lcl_wheel.

CREATE OBJECT go_vehicle.

DO ... TIMES.

CREATE OBJECT go_wheel.

* add wheel to vehicle

...

ENDDO.

2.3 Accessing Methods and Attributes(访问方法和属性)

调用方法的代码示例:

(1) 传统写法:

CALL METHOD ref->method_name

EXPORTING iv_par = val_ex ...

IMPORTING ev_par = val_im ...

CHANGING cv_par = val_chg ...

RECEIVING rv_par = val_res

EXCEPTIONS exception = val_rc... .

CALL METHOD class_name=>method_name

EXPORTING iv_par = val_ex ...

IMPORTING ev_par = val_im ...

CHANGING cv_par = val_chg ...

RECEIVING rv_par = val_res

EXCEPTIONS exception = val_rc... .

(2) SAP Web AS 6.10以后的写法:

ref->method_name(

EXPORTING iv_par = val_ex ...

IMPORTING ev_par = val_im ...

CHANGING cv_par = val_chg ...

RECEIVING rv_par = val_res

EXCEPTIONS exception = val_rc... ).

class_name=>method_name(

EXPORTING iv_par = val_ex ...

IMPORTING ev_par = val_im ...

CHANGING cv_par = val_chg ...

RECEIVING rv_par = val_res

EXCEPTIONS exception = val_rc... ).

  • ->代表实例方法,=>代表静态方法
  • 实例方法只能用对象调用,静态方法只能用类名调用

函数方法( Functional Method )定义:

  • 只有1个RETURNING参数,RETURNING参数必须为值传递
  • 在ABAP 7.40以前不能有EXPORTING或CHANGING参数
  • 允许显式( explicit )调用或隐式( implicit )调用
  • 支持隐式调用的语句包括:

    (1) MOVE, CASE, LOOP语句

    (2) Logical expressions ( IF, WHILE, CHECK, WAIT UNTIL )

    (3) Arithmetic and bit expressions ( COMPUTE )

    (4) ABAP7.50的WRITE语句

函数方法调用代码:

(1) 传统写法:

DATA gv_number TYPE i.

...

CALL METHOD lcl_vehicle=>get_n_o_vehicles

RECEIVING rv_count = gv_number.

(2) SAP R/3 4.6A以后的写法:

result = ref->func_method_name(

iv_par_1 = val_ex_1

...

Iv_par_n = val_ex_n).

result = class_name=>func_method_name(

iv_par_1 = val_ex_1

...

Iv_par_n = val_ex_n).

隐式调用函数方法代码(若有EXPORTING参数则不能隐式调用):

DATA: go_veh1 TYPE REF TO lcl_vehicle,

go_veh2 TYPE REF TO lcl_vehicle,

gv_avg_fue1 TYPE s_consum.

...

gv_avg_fue1 =

go_veh1->get_average_fue1( iv_distance = 500 iv_fuel = '50.0' )

+ go_veh2->get_average_fue1(iv_distance = 500 iv_fuel = '60.0' ).

属性访问与方法访问的写法类似。

2.4 Implementing Constructors in Local Classes(实现本地类的构造函数)

ABAP中Constructor(构造函数)的特点:

  • 所有instance constructor的名称都是CONSTRUCTOR,且在CREATE OBJECT时自动调用
  • 每个类最多只有1个instance constructor
  • constructor必须定义在PUBLIC SECTION(静态单例除外)
  • constructor只能有IMPORTING、EXCEPTION、RAISING三类参数
  • 如果constructor报出异常,则对象创建不成功且不会开辟内存区
  • 除非在子类的构造函数中调用父类的constructor,否则不能显式调用constructor
  • ABAP中没有destructor即析构函数

代码可以参考2.1节ZTEST26的代码。

2.5 Implementing Class Constructors in Local Classes(实现本地类的静态构造函数)

ABAP中Static Constructor(静态构造函数)的特点:

  • 所有的static constructor的名称都是CLASS_CONSTRUCTOR
  • ABAP允许一个类同时有动态构造函数和静态构造函数,但最多有只1个static constructor
  • Static constructor必须定义在PUBLIC SECTION
  • Static constructor不能有参数或异常
  • Static constructor不能显式调用
  • 第一次访问一个类时会调用静态构造函数,静态构造函数总是先于动态构造函数被执行

当我们第一次访问一个类且将要执行以下操作时,将触发静态构造函数:

  • When an instance of the class ( CREATE OBJECT ) is created
  • When a static attribute of the class is accessed
  • When a static method of the class is called
  • When an event handler method for an event in the class is being registered

静态构造函数与自指针代码示例:

(1) 静态构造函数

CLASS lcl_vehicle DEFINITION.

PUBLIC SECTION.

CLASS-METHODS class_constructor.

METHODS constructor IMPORTING ... .

PRIVATE SECTION.

CLASS-DATA gv_n_o_vehicles TYPE i.

ENDCLASS.

CLASS lcl_vehicle IMPLEMENTATION.

METHOD class_constructor.

...

ENDMETHOD.

METHOD constructor.

...

ENDMETHOD.

ENDCLASS.

...

gv_number = lcl_vehicle=>get_n_o_vehicles( ).

(2) 自指针

CLASS lcl_vehicle DEFINITION.

PUBLIC SECTION.

...

METHODS set_type

IMPORTING iv_make TYPE string

... .

PRIVATE SECTION.

DATA: make TYPE string.

...

ENDCLASS.

CLASS lcl_vehicle IMPLEMENTATION.

METHOD set_type.

DATA make TYPE string.

me->make = iv_make.

TRANSLATE me->make TO UPPER CASE.

make = me->make.

CONCATENATE '_' make INTO make.

...

ENDMETHOD.

ENDCLASS.

  • 与大部分语言的自指针是this不同,ABAP的自指针是me

Unit3 Inheritance and Casting(继承和类型转换)

3.1 Implementing Inheritance(实现继承)

为什么要有继承:

  • 共性的components可以统一在父类中进行管理
  • 父类向子类提供components,可以避免冗余( redundant )
  • 子类可以扩展父类的属性和方法( extensions/changes )
  • 子类跟父类存在依赖关系(用white box方式使用父类的属性和方法)
  • 虽然C++支持多继承,但是ABAP和Java一样不支持多继承,ABAP通过接口模拟多继承的效果

代码知识点

  • 继承的关键字是INHERITING FROM,在子类定义处使用
  • 重写的关键字是REDEFINITION,在要重写的METHOD名称之后使用
  1. 因为REDEFINITION只能重写子类继承的父类方法,所以PRIVATE SECTION中的方法不能使用REDEFINITION
  2. 重写只能修改方法的逻辑,而不能修改方法的参数( signature ),故不用在METHOD的名称后面再次声明该方法的参数和异常
  3. 如果我们想使用父类原来的方法,可以用super->的方法调用原来的方法
  • 父类指针是super,与自指针me用法类似

代码可参考2.1节的ZTEST26。

  • 重写( overwrite ): 指子类继承了父类的实方法,但是希望重构逻辑
  • 覆盖( override ): 指子类继承了父类的虚( virtual )方法,但是希望实现该方法
  • 重载( overload ): 指一个类中可以出现多个同名不同参的方法(ABAP不支持重载功能

Subclass Constructors子类构造函数: 子类构造函数的参数可以与父类不同,如下:

CLASS lcl_vehicle DEFINITION.

PUBLIC SECTION.

METHODS constructor IMPORTING iv_make_v TYPE string.

ENDCLASS.

CLASS lcl_vehicle IMPLEMENTATION.

METHOD constructor.

mv_make = iv_make_v.

ENDMETHOD.

ENDCLASS.

CLASS lcl_truck DEFINITION INHERITING FROM lcl_vehicle.

PUBLIC SECTION.

METHODS constructor IMPORTING iv_make_t TYPE string

Iv_cargo_t TYPE s_plan_car.

PRIVATE SECTION.

DATA mv_cargo TYPE s_plan_car.

ENDCLASS.

CLASS lcl_truck IMPLEMENTATION.

METHOD constructor.

super->constructor( iv_make_v = iv_make_t ).

mv_cargo = iv_cargo_t.

ENDMETHOD.

ENDCLASS.

子类构造函数遵循以下原则:

  1. 如果有构造函数,则使用子类构造函数并提供参数
  2. 如果无构造函数,则向上追溯,找到第一个有构造函数的祖先类,调用该构造函数并提供参数(子类无构造函数,则继承父类的构造函数与implementation)

例如:

有类lcl1,构造函数为constructor( iv_a1 : i )

lcl2继承lcl1,无构造函数

lcl3继承lcl2,构造函数为constructor( iv_a1 : i, iv_a2 : i)

那么代码将如下:

DATA: go_2 TYPE REF TO lcl_2,

go_3 TYPE REF TO lcl_3.

CREATE OBJECT go_2 EXPORTING iv_a1 = 100.

CREATE OBJECT go_3 EXPORTING iv_a1 = 100

iv_a2 = 1000.

可以看到构建lcl2的对象时,依然需要提供父类参数,而定义lcl3的构造函数时,需要考虑上层父类函数需要的参数。

注意:

PUBLIC SECTION、PROTECTED SECTION、PRIVATE SECTION,它们的顺序不能打乱

(三者的定义请参考2.1节ZTEST26代码右侧第3部分说明。)

继承与静态( Inheritance and Static Components )几个原则:

  • A class that defines a public or protected static attribute shares this attribute with all its subclasses
  • Static methods cannot be redefined(静态方法不能重写)
  • The static constructor of a superclass is executed when the superclass or one of its subclass is accessed for the first time(静态构造函数可以被子类触发)
  • A subclass can always have a static constructor, irrespective of the superclass(子类静态构造函数与父类静态构造函数无关)
  • If a subclass and its superclass both have a static constructor, then both constructors are excuted when we access the subclass for the first time. The superclass static constructor will execute first, immediately followed by the subclass static constructor.(父类静态构造函数会先执行)

3.2 Implementing Upcasts Using Inheritance(通过继承实现向上类型转换)

Upcasts: 即用父类指针指向子类对象(多态就是这样实现的)

  • 父类指针只能使用子类继承自父类或REDEFINITION的属性或方法
  • Upcasts又被称为Widening cast

(注意: 如果子类重定义了父方法,用父指针指向子对象后,调用的其实是子类方法,即多态。)

Upcasts代码示例(含静态类型与动态类型的概念):

DATA: go_vehicle TYPE REF TO lcl_vehicle,

go_truck TYPE REF TO lcl_truck.

CREATE OBJECT go_truck.

go_vehicle = go_truck.

注意:

对于go_vehicle而言,其静态类型为lcl_vehicle,其动态类型为lcl_truck。只有静态类型为动态类型同类或父类时,我们才能使用=直接赋值。

静态( Static )类型和动态( Dynamic )类型的概念:

  1. Static type:
    1. Is defined using TYPE REF TO
    2. Remains the same throughout the program flow
    3. Defines which attributes and methods can be addressed
  2. Dynamic type:
    1. Is determined by the assignment
    2. Can change during the program run
    3. Defines which implementations are carried out for inherited methods

在ABAP Debugger中,动态类型的格式如下:

object_id<\PROGRAM = program_name\CLASS = dynamic_tuype>

3.3 Implementing Polymorphism Using Inheritance(通过继承实现多态)

多态( Polymorphism )的定义: Objects from different classes react differently to the same method calls.

可以参考2.1节ZTEST26的代码。在这段代码里,我用一个in_aminal指针调用了不同动物的shout动作。

面向过程的语言实现伪多态效果( Generic Calls in the Procedural Programming Model ):

FUNCTION-POOL s_vehicle.

...

FUNCTION display_truck.

...

ENDFUNTION.

FUNCTION display_bus.

...

ENDFUNCTION.

DATA: gt_vehicles TYPE TABLE OF vehicle_type,

gs_vehicle LIKE LINE OF vehicle_list.

...

LOOP AT gt_vehicles INTO gs_vehicle.

CALL FUNCTION gs_vehicle-func_name.

ENDLOOP.

...

这里的func_name字段以字符串形式保存了各个函数的名称。因为ABAP中CALL FUNCTION之后跟的就是字符串,所以这里可以直接使用。

3.4 Implementing Downcasts Using Inheritance(通过继承实现向下类型转换)

Downcasts: 即父类指针指向了子对象之后,再用子指针指向upcasts之后的对象(就可以重新调用子对象非继承的属性和方法)

  • Downcasts又被称为narrowing cast

Downcasts代码示例:

METHOD get_max_cargo.

DATA: lo_vehicle TYPE REF TO lcl_vehicle,

lo_truck TYPE REF TO lcl_truck.

LOOP AT mt_vehicles INTO lo_vehicle.

TRY.

lo_truck ?= lo_vehicle.

CATCH cx_sy_move_cast_error.

ENDTRY.

ENDLOOP.

ENDMETHOD.

1. ?=符号代表downcasts,也可以写成MOVE ... ?TO ...。

2. downcasts如果出错,会报出cx_sy_move_cast_error这种异常,我们可以用TRY. ... CATCH ... . ENDTRY.这种语句抓捕异常。另一种避免runtime error的方法是使用runtime type identification ( RTTI ) classes。

使用继承的优点:

  • Centralized maintenance
  • Safe, generic method of access

使用继承的风险:

  • 正确的继承定义,子类必须"是"( is a )父类,如果不满足这个条件,则误用( misuse )了继承关系
  • 父类和子类的语义( semantic )关系可能有区别,比如矩形和正方形,矩形只改变宽或高的方法对于正方形就是无意义的

Unit4 Interfaces and Casting(接口和类型转换)

4.1 Defining and Implementing Local Interfaces(定义和实现本地接口)

Interfaces: can be seen as superclasses that cannot be instantiated, do not contain implementations.

接口其实和抽象类类似,允许我们通过一个指针指向子类,然后调用子类的方法实现多态。不过接口与抽象类的区别是,接口不受继承关系的限制,一些不同源的类也可以使用同样的接口。具体代码可以参考2.1节的ZTEST26代码。

使用接口的步骤:

  • Definition of the interface: 关键字INTERFACE <if_name>. … ENDINTERFACE.,注意,INTERFACE不用定义visibility,因为它的所有组件都是public components
  • Inclusion of the interface in the "server" classes: 关键字INTERFACES <if_name>.,注意,INTERFACE必须放在DEFINITION的PUBLIC SECTION,因为它只能被公有地实现
  1. Interface name
  2. Interface methods: 注意,我们可以用ALIASES关键字,给接口方法起别名(可参考ZTEST26),别名允许我们像调用普通方法那样调用接口方法,不过会影响多态性
  3. Interface components
  • Implementation of or access to interface components: 实现接口方法的关键字METHOD <if_name>~<method_name>. … ENDMETHOD.(注意: Interface Components可以用Object Reference和Interface Reference两种方式访问

用本名和别名调用接口方法的区别:

INTERFACE lif_partner.

METHODS display_partner.

DATA gv_partner_id TYPE n LENGTH 10.

ENDINTERFACE.

CLASS lcl_rental DEFINITION.

PUBLIC SECTION.

INTERFACES lif_partner.

ALIASES display_partner1

FOR lif_partner~display_partner.

PRIVATE SECTION.

ALIASES gv_partner_id

FOR lif_partner~gv_partner_id.

ENDCLASS.

DATA go_rental TYPE REF TO lcl_rental.

CREATE OBJECT go_rental… .

go_rental->lif_partner~display_partner( ).

go_rental->display_partner1( ).

  1. go_rental->lif_partner~display_partner( ).即本名调用
  2. go_rental->display_partner1( ).即别名调用
  3. 虽然INTERFACE的属性和方法都是public components,但是可以看到它们的别名是可以在Class Definition中定义visibility的,比如这里的属性别名gv_partner_id就是私有的。
  4. Interface Components可以用object reference和interface reference两种方式访问

4.2 Implementing Polymorphism Using Interfaces(通过接口实现多态)

请参考2.1节ZTEST26中通过接口实现多态的方法。

4.3 Integrating Class Models Using Interfaces(用接口存入不同的类)

Downcasts with Interfaces:

METHOD book_flight.

DATA: lo_carrier TYPE REF TO lcl_carrier,

lo_partner TYPE REF TO lif_partner.

LOOP AT mt_partners INTO lo_partner.

TRY.

lo_carrier ?= lo_partner.

CATCH cx_sy_move_cast_error.

ENDTRY.

ENDLOOP.

ENDMETHOD.

可以看到,用接口构建实例表和用父类指针构建实例表的方法其实差不多。在2.1节的ZTEST26中,我们同样构建了一个实例表。

接口允许继承关系:

  • 父接口被称为included interface,父接口是子接口的组件,故又称为component interface
  • 子接口被称为including interface,子接口是父接口的扩展,也可称为compound interface
  • 如果一个接口没有包含任何接口(即没有父接口),则称为elementary interface

对有继承关系的接口的实现代码示例:

INTERFACE lif_partner.

METHODS display_partner.

ENDINTERFACE.

INTERFACE lif_lodging.

INTERFACES lif_partner.

METHODS book_room.

ENDINTERFACE.

CLASS lcl_hotel DEFINITION.

PUBLIC SECTION.

INTERFACES lif_lodging.

ENDCLASS.

CLASS lcl_hotel IMPLEMENTATION.

METHOD lif_partner~display_partner.

ENDMETHOD.

METHOD lif_lodging~book_room.

ENDMETHOD.

ENDCLASS.

  1. 第一个实现的类,必须把其包含的接口及其父接口都实现。比如这里的lcl_hotel,尽管在DEFINITION部分只有lif_lodging,但是在IMPLEMENTATION部分必须把lif_lodging和lif_partner的方法都实现。
  2. 实现接口方法时,父类接口方法用父类接口的名称来定义,子类接口方法用子类接口的名称来定义
  3. 即使有些接口用不到也得在IMPLEMENTATION部分实现,我们可以在其中不写任何代码。
  4. 接口中可以定义的组件包括attributes、methods、events,但是不包括classes

继承接口的upcasts和downcasts代码示例:

DATA: go_hotel TYPE REF TO lcl_hotel,

go_partner TYPE REF TO lif_partner,

go_lodging TYPE REF TO lif_lodging.

go_hotel->lif_partner~display_partner( ).

go_hotel->lif_lodging~book_room( ).

*Up-Casts for generic access:

go_lodging = go_hotel.

go_lodging->lif_partner~display_partner( ).

go_lodging->book_room( ).

go_partner = go_hotel.

go_partner->display_partner().

*Down-Casts for specific access again:

go_lodging ?= go_partner.

go_hotel= ?= go_partner.

  1. 总体而言,接口的upcasts和downcasts与类差不多
  2. 有个地方需要注意,用接口名直接调用接口方法时,代码格式是<lif_name>-><method_name>.而不是<lif_name>-><lif_name>~<method_name>.

当我们无法用继承关系描述有关的类时,使用接口有以下好处:

  • Separation of the protocol interface – often defined by user and the service implementing class(预定义)
  • Safe, generic method of access(多态)
  • Ability to simulate multiple inheritance(多继承)

Unit5 Object-Oriented Events(面向对象的事件)

5.1 Implementing Events in Local Classes(实现本地类的事件)

事件( Events )的概念:

  • 类中除了包含属性、方法、类、接口,还有一类特殊的组件,叫做事件( Events )
  • 实例事件( instance events )可以被该类的实例触发( triggered ),而静态事件( static events )可以被该类本身触发
  • Events的好处是,响应方( client )不用直接调用发起方( sender )的方法,而是通过特定事件触发
  • Events允许不同的实例针对同一个事件作出不同的响应
  • UML中用<<eventHandler>>来标记事件的句柄

事件的定义与抛出代码示例:

CLASS raising_class DEFINITION.

PUBLIC SECTION.

EVENTS eventname

[ EXPORTING value( ev_par ) TYPE typename ].

ENDCLASS.

CLASEE raising_class IMPLEMENTATION.

METHOD methodname.

RAISE EVENT eventname [ EXPORTING ev_par = lv_par ].

ENDMETHOD.

ENDCLASS.

  1. EVENTS用于定义普通事件,CLASS-EVENTS用于定义静态事件
  2. 事件的参数只能是EXPORTING型的,且只能passed by value
  3. 静态方法只能抛出( trigger )静态事件
  4. 实例方法可以抛出所有类型事件
  5. RAISE EVENT时需要给EVENT参数赋值

事件的处理代码示例:

CLASS lcl_rental DEFINITION.

METHODS on_vehicle_created

FOR EVENT vehicle_create

OF lcl_vehicle

IMPORTING SENDER.

ENDCLASS.

CLASS lcl_rental IMPLEMENTATION.

METHOD on_vehicle_created.

...

ENDMETHOD.

ENDCLASS.

  1. FOR EVENT用于定义事件,OF用于定义类或者接口
  2. IMPORTING用于接收事件抛出的参数,所有事件都会默认抛出一个叫SENDER的参数,SENDER参数是一个引用,指向抛出事件的对象(注意: SENDER是实例事件特有的默认参数
  3. 通常来讲,发起方通过事件触发响应方的函数,但是我们也能用CALL METHOD显式调用响应方的函数

句柄的注册( registration )与注销( revoke )代码示例:

SET HANDLER ref_handler->on_eventname

[ FOR ref_sender | FOR ALL INSTANCES ]

[ ACTIVATION flag ]

  1. ref_handlr->on_eventname指响应方的类名和方法名
  2. ref_sender指发起方的实例名
  3. FOR ALL INSTANCES会响应所有抛出该EVENT的实例,这是唯一一种为尚未实例化的对象注册句柄的方法(该语句仅用于实例事件的HANDLERS
  4. ACTIVATION参数为X时表示注册,为空值时表示注销,默认为X(该语句可用于实例事件或静态事件的HANDLERS
  5. lcl_rental这里,为了方便句柄的注册,将SET HANDLER代码写在了构造函数里,其实也可以写在类的外面。

CLASS lcl_rental IMPLEMENTATION.

METHOD constructor.

...

SET HANDLER me->on_vehicle_created

FOR ALL INSTANCES.

ENDMETHOD.

ENDCLASS.

其它重要知识点:

  • 定义了事件的类或对象都有一个内表,称为handler table,里面存放了所有响应它们的事件的handler methods。对于instance handler methods,handler table还会存放引用( references ),指向注册事件的对象。
  • 对象如果注册了event handling,则不再被garbage collector删除,哪怕已经没有引用指向它们了。此时只能用FREE语句手动删除。

事件( Events )的可见性( Visibilities ):

  • PUBLIC: Event can be handled anywhere
  • PROTECTED: Event can only be handled within its own class or its subclasses
  • PRIVATE: Event can only be handled within its own class

句柄函数( Handler Methods )的可见性( Visibilities ):

  • PUBLIC: Handler method can be registered anywhere in the program
  • PROTECTED: Handler method can be registered within its own class or its subclass
  • PRIVATE: Handler method can only be registered within its own class

需要注意的是,Event-Handler的可见性只能等于其相应的Events或比该Events更加严格。

(更全的代码,可以参考TAW12-2 Unit5 Solution14。)

5.2 Implementing Events in Local Interfaces(实现本地接口的事件)

在接口中定义事件的方法(因为接口不实现,就不考虑抛出事件或句柄的逻辑):

  1. Define event in an interface
  2. Trigger the interface event in implementing classes ( 即Server Class或Sender Class )
  3. Handle interface event in handler class( 即Client Class )
  4. Register event handling

(总体而言,与类的事件处理类似。参考TAW12-1 250页的代码。)

Unit6 Object-Oriented Repository Objects(面向对象的仓库对象)

6.1 Creating Global Classes(创建全局类)

创建全局类的事务代码: SE24

通过本地类创建全局类的方法: SE24->对象类型->导入(下一节有详细过程)

Class Builder常用按钮:

  • Source Code按钮: 允许用户查看全局类的代码
  • Attributes页签: 定义属性,其中有个字段为右键图标,允许用代码定义属性类型(对于内表型属性比较有用,详情可以参考6.3节"在全局类中定义本地类型")
  • Methods页签: 定义方法(Methods可以分别传输,因为它们的IMPLEMENTATION是放在不同的INCLUDE文件中的)

    (1) Method需要定义参数和异常,还需要编写逻辑代码( 点击Source code按钮 )

    (2) Signature按钮运行用户在编写代码时查看参数与异常

    (3) Goto->Method definition按钮允许用户修改属性

Navigation Area( SE80 ): SE80允许我们用右键创建类的组件,也可以通过Class->Print or Method->Print打印选中部分的代码。

Class Builder Testing Environment( SE24测试运行环境 ):

  • 进入方式为Class->Run->In Test Environment,或者按功能按钮或F8
  • 静态属性和静态方法可以直接测试
  • 普通属性和普通方法需要创建对象再测试,创建方式为Create Instance按钮
  • Execute Method按钮用于执行方法
  • 触发事件的步骤如下:

    (1) Select an event

    (2) Choose Handler to register a standard method for the event

    (3) Call a method in which the event trigger was implemented

    (4) The event that was triggered and all of the exported actual parameters are displayed in a list

在ABAP Editor中使用全局类的方法:

DATA: go_hotel TYPE REF TO cl_hotel.

CREATE OBJECT go_hotel.

CALL METHOD go_hotel->display_attributes( ).

  1. 用TYPE REF TO可以定义全局类引用
  2. 用CALL METHOD可以调用全局类方法
  3. 如果我们记不住类的方法有哪些参数,可以用以下方法插入该方法: Utilities->Settings->Pattern,选择Call Method checkbox中的Functional Writing Style.

(Class Builder的使用详细方法请参考第三方资料。)

6.2 Defining and Implementing Global Interfaces(定义和实现全局接口)

全局接口的创建: 与全局类类似,可以使用SE24也可以使用SE80。当我们的名称以ZIF_或YIF_开头时,系统会自动弹出创建接口的界面,而如果以其它的Z*或Y*开头时,系统则会自动弹出创建类的界面。

接口的实现: 在全局类的Interfaces页签,定义要使用的接口,在Methods页签可以看到接口的方法被带入了,此时双击方法名就可以编写逻辑代码。(此处有个窍门,在Utilities->Settings中有个按接口和父类分组的复选框,可以让Methods页签里的方法按照接口和父类进行分组显示。)

将本地类和本地接口导入Class Builder变成全局类和全局接口的方法:

  1. On the SAP Easy Access screen, choose Tools->ABAP Workbench->Development->Class Builder(或事务代码SE24)
  2. On the Class Builder: Initial Screen, choose Object type->Import->Local Classes in program
  3. In the Import form Program dialog box, enter the name of the main program and, if the local classes and interfaces were defined within include programs, select the Explode INCLUDEs checkbox
  4. Choose the Display Classes/Interfaces button
  5. Enter names for the global classes and interfaces that you want to create
  6. If applicable, remember the customer namespace
  7. To select all the global names, choose the Select All button
  8. Choose the global classes and interfaces that you want to create and choose the Import button

6.3 Implementing Inheritance in Global Classes(在全局类中实现继承)

在SAP NetWeaver 7.0 Enhancement Package2之后,Class Builder可以为已存在的代码创建UML。

  • 首先我们需要在Utilities菜单中选择"显示对象列表"
  • 如果我们选择一个package再右键选择Display->UML Class Diagram,就可以绘制包括全局类的UML
  • 如果我们选择一个program再右键选择创建,就可以绘制本地类和全局接口的UML,但是不包括全局类
  • 我们也可以绘制ABAP Dictionary中的对象的UML

Class Builder(SE24)中跟继承有关的按钮包括:

  • Properties页签Superclass按钮,用于定义父类
  • Properties页签Final复选框,一旦选中则为最终类,不能再被继承
  • Methods页签Redefine按钮,用于重定义父类方法(从父类继承的方法为蓝色,重写过的方法为黑色)
  • Methods页签Undo Redefine按钮,用于撤销重定义
  • 当子类需要定义构造函数时,进入编辑状态再按下CONSTRUCTOR按钮(在application toolbar那里)

在全局类中定义本地类型:

  • 本质上讲,本地类与本地类型都放在全局类的INCLUDE文件中
  • 有两个INCLUDE文件:

    (1) Local Definition/Implementations(老版为Local Class Implementations): 通过点击同名按钮进入,可以定义非私有的local interfaces、local class definitions、local types、local class implementations,甚至可以包括其它INCLUDE文件

    (2) Class Relevant Local Definitions(老版为Local Types): 在定义属性时,Typing字段选择Type Ref To,输入要自定义的类型名称,然后选择右箭头按钮,系统将自动进入该INCLUDE文件并创建属性与类型的代码,用户可以对代码进行手工调整。可定义私有的local interfaces、local class definitions、local types、local class implementations,主要用于复杂的方法参数

类组件的显示技巧:

  • 当我们点击Settings按钮或者Utilities->Settings,勾选Group by Classes and Interfaces,我们就能按父类和接口显示方法
  • 如果我们选择Sort按钮,则可以通过三级排序,每级排序有五个选项:

    (1) No sort

    (2) Name ( A ... Z )

    (3) Name ( Z ... A )

    (4) Visibility

    (5) Scope ( static/instance )

Unit7 ABAP Object-Oriented Example(面向对象的ABAP示例)

7.1 Using the ABAP List Viewer ( ALV )(使用ALV)

ALV Grid Control: It is a tool to display non-hierarchical lists in a standardized form. It contains a number of interactive standard functions that users of lists often need ( print, export ... ).

Container Controls: They provide the technical connection between the screen and application controls ( ALV Grid Control, Tree Control, Picture Control ).

几种CONTAINER对比:

  • CL_GUI_CUSTOM_CONTAINER: 普通CONTAINER
  • CL_GUI_DOCKING_CONTAINER: 锚定式CONTAINER,允许用户拉动宽度
  • CL_GUI_SPLITTER_CONTAINER: 分割式CONTAINER,允许把屏幕分割成不同的GRID

创建ALV的代码示例:

DATA: go_container TYPE REF TO cl_gui_custom_container,

go_grid TYPE REF TO cl_gui_alv_grid.

CREATE OBJECT go_container

EXPORTING container_name = 'CONTAINER_1'.

CREATE OBJECT go_grid

EXPORTING i_parent = go_container.

go_grid->set_table_for_first_display(

i_structure_name = 'SAPLANE'

it_outtab = gt_saplane ).

具体步骤如下:

  1. Use the full screen editor of the Screen Painter to define a custom control area on your screen
  2. Create an instance of the class CL_GUI_CUSTOM_CONTAINER, and transfer the name of the custom control area to the constructor
  3. Create an instance of the class GL_GUI_ALV_GRID and transfer the reference to the custom control instance to the constructor
  4. Call the method SET_TABLE_FOR_FIRST_DISPLAY of the grid control instance and transfer the internal standard table to it, which contains the data to be displayed

注意:

  1. 如果我们使用ABAP Dictionary中的global row type定义内表,则可直接使用,ALV Grid Control会自动创建field catalog
  2. 如果我们要在代码中自定义内表,则内表必须为程序的全局变量或者类的公共属性
  3. 如果我们要刷新显示,只需要调用REFRESH_TABLE_DISPLAY即可

响应ALV的代码示例(双击事件):

DATA: go_grid TYPE REF TO cl_gui_alv_grid.

CLASS lcl_event_handler DEFINITION.

PUBLIC SECTION.

METHODS: on_double_click

FOR EVENT double_click

OF cl_gui_alv_grid

IMPORTING es_row_no e_column.

...

ENDCLASS.

CLASS lcl_event_handler IMPLEMENTATION.

METHOD on_double_click.

...

MESSAGE i010(bc401) WITH es_row_no-row_id

e_column-fieldname.

ENDMETHOD.

ENDCLASS.

DATA: go_handler TYPE REF TO lcl_event_handler.

CREATE OBJECT go_handler.

SET HANDLER go_handler->on_double_click FOR go_grid.

如果要响应ALV的事件,我们需要:

  1. 定义响应类,定义响应事件的Methods
  2. 实现响应类,实现响应事件的Methods
  3. 创建一个响应类的实例
  4. 注册响应类的Handler Methods(Handler Methods可以是实例的也可以是静态的

注意: ABAP Workbench的Enjoy Demo Center向用户提供了各种标准的DEMO(事务代码DWDM)。

7.2 Describing Business Add-Ins ( BAdIs )(描述BAdI)

BAdIs的概念: With a BAdI, an SAP application provides the enhancement option through an interfaces and an adapter class implementing that interface.

  • Multiple BAdI implementations are possible.(BAdI本质就是一个增强接口,用户可以创建对应的增强类,所以一个BAdI可以有多个实施。而EXIT型增强则只能有一个。)
  • BAdI的接口名称均为IF_EX_<badi>的格式,其adapter class的名称均为CL_EX_<badi>,如果一个接口有多个实施类,它们的顺序将难以确定( no predefined processing sequence )

定义BAdI的过程:

  1. Run the transaction SE18 or, on the SAP Easy Access screen, choose Tools->ABAP Workbench->Utilities->Business Add-Ins->Definition. ( As of ABAP Netweaver 7.0, you cannot directly create a BAdI. First, you must create an Enhancement Spot. )
  2. Enter the Enhancement Spot name and choose the Create button. ( A Create Enhancement Spot dialog displays. )
  3. In the Create Enhancement Spot dialog, enter a short description, and choose the Creation of an Enhancement button. Save the enhancement to your package.
  4. Choose the Create BAdI button. ( A Create BAdI Definition dialog displays. )
  5. Enter a short description and choose the Continue button.
  6. In the BAdI Definition screen area, expand the node, and double-click Interface.
  7. Enter your interface name ZIF_BC401_00_BADI_DEMO and press Enter. ( A Class/Interface dialog box displays. )
  8. On the Class/Interface dialog, choose Yes to create the interface. Save it to your package, then save the enhancement spot. ( The class builder will display on the Method tab page. )
  9. Enter an instance method and a description, and activate the interface.

The Search for BAdIs(搜索BAdI的方法):

  • You can use the Repository Information System( transaction SE84 )
  • You can use the Application Hierarchy( transaction SE81 )
  • You can use the Customizing Guide( Implementation guide for transaction SPRO )
  • You can search in the application source code for the CL_EXITHANDLER=>GET_INSTANCE statement
  • You can search in the application source code for the occurrence of the BAdI interfaces with the naming convention IF_EX_
  • As of SAP Netweaver AS 7.0, you can search in the application source code for occurrences of the GET BADI statement.

附: 进入源代码的方法。首先进入一个事务代码,按F1,然后选择Technical Information按钮。找到源代码所在程序,双击程序名。然后使用global search->Edit->Find Next。注意,不能用Edit->Find/Replace( Ctrl + F ),因为后者不包括INCLUDE程序。

To Implement a BAdI(实施BAdI的方法):

  1. Run the transaction code SE19.
  2. Enter the BAdI definition name( or Enhancement Spot name ) and choose the Create button.
  3. Enter an implementation name and choose the OK button.
  4. Enter a description text and choose the Save button.
  5. Double-click the name of the implementing class to navigate to the class and implement your source code there.

Unit8 Class-Based Exceptions(异常类)

8.1 Explaining Class-Based Exceptions(解释异常类)

异常类示例代码:

REPORT ZTEST25.

DATA:
  PROXY TYPE REF TO ZDDFICO_CO_IERP_SUPPLIER_SERVI,
  INPUT TYPE ZDDFICO_IERP_SUPPLIER_SERVICE.

DATA: WEBSERVICE_SYS_ERROR TYPE REF TO CX_AI_SYSTEM_FAULT,           "系统错误
      WEBSERVICE_APP_ERROR TYPE REF TO CX_AI_APPLICATION_FAULT,      "应用错误
      WEBSERVICE_NUM_ERROR TYPE REF TO CX_SY_CONVERSION_NO_NUMBER.   "字符转换数字错误

DATA: Z_PROXY  TYPE REF TO ZMES_CO_SYSTEM_WEB_SERVICES_SO,
      Z_INPUT  TYPE ZMES_RECEIVER_IDOC_XML_SOAP_IN,
      Z_OUTPUT TYPE ZMES_RECEIVER_IDOC_XML_SOAP_OU.

DATA:
  ZTEST_ERROR TYPE REF TO ZCX_TEST.

START-OF-SELECTION.

IF PROXY IS INITIAL.

TRY.
        CREATE OBJECT PROXY
          EXPORTING
            LOGICAL_PORT_NAME = 'ZPORT1'.

CATCH CX_AI_SYSTEM_FAULT INTO WEBSERVICE_SYS_ERROR.
      CATCH CX_AI_APPLICATION_FAULT INTO WEBSERVICE_APP_ERROR.
      "与CATCH不同,CATCH后如果不再RAISE,外层收不到错误
      "而CLEANUP处理完后,外层还可以继续CATCH到错误
      CLEANUP.
        MESSAGE 'middle term process!' TYPE 'S'.
    ENDTRY.

IF WEBSERVICE_SYS_ERROR IS NOT INITIAL.
      "抛出异常类,此时会隐式创建一个类对象
      RAISE EXCEPTION TYPE CX_AI_SYSTEM_FAULT.
    ENDIF.

IF WEBSERVICE_APP_ERROR IS NOT INITIAL.
      "直接抛出类对象
      RAISE EXCEPTION WEBSERVICE_APP_ERROR.
    ENDIF.

ENDIF.

IF Z_PROXY IS INITIAL.
    TRY.
        CREATE OBJECT Z_PROXY
          EXPORTING
            LOGICAL_PORT_NAME = 'ZMES_PORT0'.

CATCH CX_AI_SYSTEM_FAULT INTO WEBSERVICE_SYS_ERROR.
      CATCH CX_AI_APPLICATION_FAULT INTO WEBSERVICE_APP_ERROR.
    ENDTRY.

IF WEBSERVICE_SYS_ERROR IS NOT INITIAL.
      "创建新的异常,并给出构造函数参数
      CREATE OBJECT ZTEST_ERROR
        EXPORTING
          TEXTID = ZCX_TEST=>ZCX_TEST      "即TEXTID,默认为本异常类同名ID
          PREVIOUS = WEBSERVICE_SYS_ERROR. "即本异常之前的异常是谁

"抛出新的异常的文本
      DATA: MSSG TYPE STRING.
      CALL METHOD ZTEST_ERROR->GET_TEXT
        RECEIVING
          RESULT = MSSG.
      MESSAGE MSSG TYPE 'S' DISPLAY LIKE 'E'.
    ENDIF.

IF WEBSERVICE_APP_ERROR IS NOT INITIAL.

"隐式抛出新的异常对象
      RAISE EXCEPTION TYPE ZCX_TEST
        EXPORTING
          TEXTID = ZCX_TEST=>ZCX_TEST.

ENDIF.

ENDIF.

  1. 传统异常: based on sy-subrc

    异常类: can be raised and handled in all processing blocks

  2. 与传统异常直接RAISE不同,异常类的关键字如下:

    RAISE EXCEPTION TYPE cx_exception

    [ EXPORTING attr1 = ...

    attr2 = ... ].

    然后配合TRY. ... CATCH ... ENDTRY.处理。

  3. 在左侧代码中,我们能看到显式抛出和隐式抛出两种形式。ZCX_TEST是SE24创建的继承了CX_STATIC_CHECK的异常类。
  4. 异常类的继承关系:

    根节点: CX_ROOT,所有异常类的祖先,但禁止被直接继承

    二级节点:

    (1) CX_NO_CHECK,不处理,只RAISE到最外层

    (2) CX_DYNAMIC_CHECK,可选处理

    (3) CX_STATIC_CHECK,必须处理或RAISE

    特殊子节点: CX_SY_,即runtime environment exception classes

    特殊接口: IF_MESSAGE,是根节点实现的接口,提供了GET_TEXT这样的方法

  5. 完整的TRY. ... ENDTRY.模块

    TRY.

    ...

    CATCH cx_... cx_... cx_... [ INTO gx_excl ].

    ...

    CATCH cx_... [ INTO gx_exc2 ].

    ...

    CLEANUP.

    ...

    ENDTRY.

    (1)需要注意CLEANUP关键字,当本地没有local exception handler时,就执行该模块的代码。CATCH之后如果不再RAISE,则外层不再收到异常,而CLEANUP之后,外层还能继续CATCH到异常。

    (2)INTO关键字,允许生成异常对象。默认情况下,没有这个关键字系统就不会生成异常对象,除非在debug settings里设置always create the exception object。

  6. 我们在ZCX_TEXT中定义了一些异常文本,ZCX_TEXT=>ZCX_TEXT就是其中之一。排在第一的文本对象名称一定与异常类名称相同。
  7. TRY....ENDTRY.可以任意嵌套,且放在CLEANUP块。( You can nest TRY-ENDTRY structures to any depth. )
  8. CLEANUP代码块,在本层未CATCH到对应异常,但是外层CATCH到对应异常时触发。我们可以在该代码块内清空之前的某些数据。
  9. 触发CLEANUP时会略过本层后续代码,直接跳入外层的CATCH代码块。

8.2 Defining and Raising Exceptions(定义和抛出异常)

创建异常类的步骤:

  1. 进入SE24
  2. 定义类名,前缀为ZCX_
  3. 创建类
  4. 选择类型为异常类

    有两种消息模式可选: (1) Online Text Repository ( OTR ),即直接文本; (2) Message Class,即消息类

    如果选择类Message Class,则需要在SE91创建消息类,以及消息编号

    如果选择OTR,则创建CHAR型文本并有一个文本ID

  5. 给出异常类描述
  6. 选择默认父类为CX_STATIC_CHECK,并保存
  7. 选择package
  8. 选择传输方向
  9. 设置异常类属性
  10. 进入Texts页签,创建异常文本,我们可以对异常文本分配消息类、消息编号、参数变量

    带参数的消息格式如下: This airplane type &planetype& is unknown,中间的字符会根据参数值变化

ZCX_TEST中就有三个异常文本,其中第一个异常文本总是与异常类同名。在ZTEST25的代码中,我们通过ZCX_TEST=>ZCX_TEST的方法使用了这个异常文本。

显式抛出异常,先创建异常类,再用RAISE EXCEPTION抛出异常类。

隐式抛出异常,直接RAISE EXCEPTION TYPE <exception_class> [ EXPORTING ... ].

定义与获取异常文本的代码示例:

RAISE EXCEPTION TYPE cx_invalid_planetype.

1. 第一种方式,模式使用异常类同名的异常文本

2. 第二种方式,手动选择要用的异常文本

3. 第三种方式,使用标准的get_text()函数获取异常文本

RAISE EXCEPTION TYPE cx_invalid_planetype

EXPORTING

TEXTID = cx_invalid_planetype=>cx_invalid_planetype2.

DATA lx_exception TYPE REF TO cx_invalid_planetype.

CATCH cx_invalid_planetype INTO lx_exception.

Lv_text = lx_exception->get_text( ).

抛出异常与捕获异常的代码示例:

METHODS constructor

IMPORTING ...

RAISING cx_exc.

RAISE EXCEPTION TYPE cx_exc

EXPORTING ... .

CLEANUP与CATCH在多层级时的区别:

  • 如果某层有CLEANUP关键字,则出现exception时会进行中途处理,与CATCH相比,它没有捕获和停止功能,执行完后回到上层CATCH处继续运行
  • CATCH处理后,不再做RAISE操作的话,上层CATCH不到本层的异常

8.3 Implementing Advanced Exception Handling Techniques(高级异常处理技术)

因为CX_ROOT不能直接继承,所以我们定义的异常类都继承自三个子类:

  • CX_STATIC_CHECK: 必须handle或者用RAISING(在method定义处)抛出,如果不做处理,则syntax check会给出warning信息
  • CX_DYNAMIC_CHECK: handle或RAISING都是可选的,syntax check不会给出warning信息(部分CX_SY_型异常类是该类型的)
  • CX_NO_CHECK: 只能handle,不能RAISING,如果不做处理,会自动向上层抛出直到某层做了做了handle操作(部分CX_SY_型异常类是该类型的)

CATCH之后可以使用的技术:

  • 在ENDTRY之后可继续执行的动作有:

    (1) Ignoring the exception( do nothing )( Continue program, do not propagate an exception )

    (2) Issuing a warning

    (3) Writing to a protocol

    (4) Correcting the situation( Remove the Cause of error )

  • 消除错误原因并重新开始的动作有:

    (1) RETRY ( SAP NetWeaver 7.0 EhP 2 )

    (2) RESUME ( SAP NetWeaver 7.0 EhP 2 )

  • 抛出异常的动作有:

    (1) RAISE EXCEPTION <obj_ref>.

    (2) RAISE EXCEPTION TYPE <exc_class>.

RETRY语句示例:

主程序:

DATA:

go_plane TYPE REF TO lcl_airplane,

gx_exc TYPE REF TO cx_exc.

TRY.

CREATE OBJECT go_plane

EXPORTING ... .

CATCH cx_exc INTO gx_exc.

<remove cause of the exception>

RETRY.

ENDTRY.

lcl_airplane类中有关代码:

METHODS constructor

IMPORTING ...

RAISING cx_exc.

METHOD constructor.

RAISE EXCEPTION TYPE cx_exc

EXPORTING ... .

ENDMETHOD.

RETRY会在处理完异常后,再次回到TRY.语句行。

RESUME语句示例:

主程序:

TRY.

CREATE OBJECT go_plane

EXPORING ... .

...

<other statements>

CATCH BEFORE UNWIND cx_exc INTO gx_exc.

IF gx_exc->is_resumable = 'X'.

RESUME.

ENDIF.

...

ENDTRY.

RESUME语句,会跳到上次出错语句之后的那一句继续执行,比如示例中CREATE OBJECT出错以后会跳转到<other statements>对应的的语句。

lcl_airplane类中有关代码:

METHODS constructor

IMPORTING ...

RAISING RESUMABLE ( cx_exc ).

METHOD constructor.

...

CALL METHOD me->get_tech_attr

EXPORTING ...

IMPORTING ... .

...

ENDMETHOD.

METHODS get_tech_attr

IMPORTING ...

EXPORTING ...

RAISING RESUMABLE ( cx_exc ).

METHOD get_tech_attr.

...

RAISE RESUMABLE EXCEPTION TYPE cx_exc

EXPORTING ... .

...

ENDMETHOD.

RESUME语句的使用前提:

  • CATCH语句后必须跟着BEFORE UNWIND关键字,这个关键字表明该异常可能会RESUME,如果后面没有RESUME语句,系统会删除该异常的上下文( context )
  • 抛出异常需要使用RAISE RESUMABLE EXCEPTION而不是RAISE EXCEPTION,这句话会准备raising processing lock
  • 各个层级都需要用RAISING RESUMABLE抛出该异常(比如示例中constructor和get_tech_attr)
  • IS_RESUMABLE字段用于识别是否能够RESUME
  • 如果强行RESUME不能复位的异常,将报runtime error(CX_SY_ILLEGAL_HANDLER)

MAPPING传递异常类:

第一层代码:

METHODS get_tech_attr

IMPORTING ...

EXPORTING ...

RAISING ex_exc.

METHOD get_tech_attr.

...

RAISE EXCEPTION TYPE cx_exc

EXPORTING ... .

ENDMETHOD.

第二层代码:

METHOD constructor

IMPORTING ...

RAISING cx_exc2.

METHOD constructor.

...

TRY.

CALL METHOD me->get_tech_attr.

CATCH ex_exc INTO lx_exc.

RAISE EXCEPTION TYPE cx_exc2

EXPORTING

previous = lx_exc.

ENDTRY.

ENDMETHOD.

第三层代码:

TRY.

CREATE go_plane

EXPORTING ... .

...

CATCH cx_exc2 INTO gx_exc.

...

ENDTRY.

传递异常类,可以看到抛出ex_exc2时,把previous设为了ex_exc。主程序可以用previous属性进行追踪。

Re-Raise Exceptions再抛出:

第一层代码:

METHODS get_tech_attr

IMPORTING ...

EXPORTING ...

RAISING cx_exc.

METHOD get_tech_attr.

...

RAISE EXCEPTION TYPE cx_exc

EXPORTING ... .

...

ENDMETHOD.

第二层代码:

METHODS constructor

IMPORTING ...

RAISING cx_exc.

METHOD constructor.

TRY.

CALL METHOD me->get_tech_attr.

CATCH cx_exc INTO lx_exc.

...

RAISE EXCEPTION lx_exc.

ENDTRY.

ENDMETHOD.

第三层代码:

TRY.

CREATE OBJECT go_plane

EXPORTING ... .

...

CATCH cx_exc INTO gx_exc.

...

ENDTRY.

这里我们隐式抛出了一个异常类,捕获之后又显式抛出了一个异常类。

Unit9 Object-Oriented Design Patterns(面向对象设计模式)

9.1 Implementing Advanced Object-Oriented Techniques(实现面向对象高级技术)

抽象类(Abstract Class): contains both definition and implementation but cannot be instantiated.

REPORT ZTEST34.

CLASS lcl_test_root DEFINITION ABSTRACT.
  PUBLIC SECTION.
  METHODS:
    action1 ABSTRACT,
    action2.
  CLASS-METHODS:
    action3.
ENDCLASS.

CLASS lcl_test_root IMPLEMENTATION.

"action1必须子类用REDEFINITION重定义,而不能直接实施

METHOD action2.
    WRITE: / 'action2'.
  ENDMETHOD.

METHOD action3.
    WRITE: / 'action3'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_test_level1 DEFINITION INHERITING FROM lcl_test_root.
  PUBLIC SECTION.
  METHODS:
    action1 REDEFINITION.
  CLASS-METHODS:
    action4.
ENDCLASS.

CLASS lcl_test_level1 IMPLEMENTATION.
  METHOD action1.
    WRITE: / 'action1'.
  ENDMETHOD.

METHOD action4.
    WRITE: / 'action4'.
  ENDMETHOD.
ENDCLASS.

DATA: go_test_level1 TYPE REF TO lcl_test_level1,
      go_test_root TYPE REF TO lcl_test_root.

START-OF-SELECTION.

CREATE OBJECT go_test_level1.
go_test_root = go_test_level1.

go_test_level1->action1( ).
go_test_level1->action2( ).
lcl_test_level1=>action3( ).
lcl_test_level1=>action4( ).

go_test_root->action1( ).
go_test_root->action2( ).
lcl_test_root=>action3( ).

  1. 抽象类与接口类似,但是可以通过继承关系约束子类
  2. 抽象类可定义抽象方法,它们的定义必须跟ABSTRACT关键字
  3. 抽象的方法不能在IMPLEMENTATION中实现,而是在子类中通过REDEFINITION实现
  4. 抽象类可以有非抽象的组件,比如普通组件或者静态组件,这些组件需要在IMPLEMENTATION中实现
  5. 抽象方法的调用与接口方法类似(经过ZTEST34测试,不论抽象类还是子类,其静态方法只能通过类名调用
  6. 抽象类可以不包含任何抽象方法
  7. 静态方法不能抽象,因为不能REDEFINITION

最终类(Final Classes)

CLASS lcl_test DEFINITION FINAL

[ INHERITING FROM ... ].

...

ENDCLASS.

CLASS lcl_test DEFINITION.

...

METHODS ... FINAL ... .

...

ENDCLASS.

  1. 最终类与普通类类似,但是不能被继承
  2. 最终类可以定义最终方法,它们都需要跟上FINAL关键字,最终方法不能被REDEFINITION
  3. 如果一个类既是抽象类又是最终类,那么它只能包含静态组件(抽象类必须通过子类实例化,而最终类没有子类,故抽象类加最终类只能含有静态组件)

内表与类对象是兼容的,代码示例:

TYPES:

BEGIN OF gty_s_vehicle,

make TYPE string,

mode1 TYPE string,

ref TYPE REF TO lcl_vehicle

END OF gty_s_vehicle,

gty_t_vehicle TYPE TABLE OF gty_s_vehicle.

DATA:

gs_vehicle TYPE gty_s_vehicle,

gt_vehicle TYPE gty_t_vehicle.

READ TABLE gt_vehicles INTO gs_vehicle WITH KEY make = ... mode1 = ... .

CALL METHOD gs_vehicle-ref-> ... .

LOOP AT gt_vehicles INTO gs_vehicle WHERE make = ... .

...

ENDLOOP.

只读的公共属性,代码示例:

CLASS lcl_vehicle DEFINITION.

PUBLIC SECTION.

DATA: make TYPE string READ-ONLY,

mode1 TYPE string READ-ONLY.

ENDCLASS.

DATA: go_vehicle TYPE REF TO lcl_vehicle,

gt_vehicles TYPE TABLE OF REF TO lcl_vehicle.

READ TABLE gt_vehicles INTO go_vehicle

WITH KEY table_line->make = ...

table_line->mode1 = ... .

LOOP AT gt_vehicle INTO go_vehicle

WHERE table_line->make = ... .

...

ENDLOOP.

  1. 公共属性可以直接访问,如果我们不希望这些属性被修改,可以使用READ-ONLY关键字
  2. 如果我们的内表只有一列且该列不是structure,那么可以用TABLE_LINE获取内表的行,TABLE_LINE只有一列且该列没有名称

Navigation Methods and Chain Method Calls:

Navigation Methods:

(假设lcl_vehicle是lcl_rental的组件)

DATA: go_rental TYPE REF TO lcl_rental,

go_vehicle TYPE REF TO lcl_vehicle.

go_vehicle = go_rental->get_vehicle( ... ).

go_vehicle->display_attributes().

  1. 示例中lcl_vehicle引用是lcl_rental的组件,这种关系被称为association
  2. Navigation Methods,可以先将组件对象读出,再调用组件对象的方法
  3. Chain Method Calls,通过层级关系直接调用组件对象的方法

Chain Method Call:

DATA: go_rental TYPE REF TO lcl_rental.

go_rental->get_vehicle( ... )->display_attributes( ).

用NEW创建对象:

DATA: go_vehicle TYPE REF TO lcl_vehicle.

CREATE OBJECT go_vehicle

EXPORTING

iv_make = 'VM'

iv_mode1 = '1200'.

go_vehicle = NEW #(

iv_make = 'VM'

iv_mode1 = '1200'

).

go_vehicle = NEW lcl_truck(

iv_make = 'DAIMLER'

iv_mode1 = 'ACTROS'

iv_cargo = 25000

).

DATA: gt_vehicles TYPE TABLE OF REF TO lcl_vehicle.

APPEND NEW #(

iv_make = 'PORSCHE'

iv_mode1 = '911'

) TO gt_vehicles.

DATA: go_rental TYPE lcl_rental.

go_rental->add_vehicle( io_vehicle = NEW lcl_truck(

iv_make = 'DAIMLER'

iv_mode1 = 'ACTROS'

iv_cargo = 25000 ) ).

DATA: go_alv TYPE REF TO cl_gui_alv_grid.

go_alv = NEW # (

i_parent = NEW cl_gui_custom_container(

container_name = 'CONTAINER_1' ) ).

  1. NEW #为隐式类型说明,以引用类型为创建类型
  2. NEW <class_name>为显式类型说明,类名直接给出
  3. NEW可以配合APPEND工作

    NEW的对象可以作为参数使用

Dynamic Type of Object Reference(AS ABAP 7.50以后增加的IS INSTANCE OF和CASE TYPE OF语句):

IS INSTANCE OF语句的用法:

DATA: gt_vehicles TYPE TABLE OF REF TO lcl_vehicle,

go_vehicle TYPE REF TO lcl_vehicle,

go_truck TYPE REF TO lcl_truck.

LOOP AT gt_vehicles INTO go_vehicle.

IF go_vehicle IS INSTANCE OF lcl_truck.

go_truck ?= go_vehicle.

ENDIF.

ENDLOOP.

CASE TYPE OF语句的用法:

DATA: gt_vehicles TYPE TABLE OF REF TO lcl_vehicle,

go_vehicle TYPE REF TO lcl_vehicle,

go_truck TYPE REF TO lcl_truck,

go_bus TYPE REF TO lcl_bus.

LOOP AT gt_vehicles INTO go_vehicle.

CASE TYPE OF go_vehicle.

WHEN TYPE lcl_truck INTO go_truck.

...

WHEN TYPE lcl_bus.

...

WHEN OTHERS.

ENDLOOP.

Visibility of the Instance Constructor(实例化的可见性):

CLASS lcl_test1 DEFINITION CREATE PUBLIC.

...

ENDCLASS.

CLASS lcl_test2 DEFINITION CREATE PROTECTED.

...

ENDCLASS.

CLASS lcl_test3 DEFINITION CREATE PRIVATE.

...

ENDCLASS.

  1. CREATE PUBLIC: 代表任意用户可以实例化该类对象,这也是默认设置
  2. CREATE PROTECTED: 本类及其子类可实例化该类对象
  3. CREATE PRIVATE: 本类内部才能构造(单件采用这种方式,创建单件的静态方法称为Factory-Methods)
  4. 在SAP NetWeaver 7.0以前,constructor均在PUBLIC SECTION. 不过SAP NetWeaver 7.0之后,constructor可以放到其它SECTION了。但是,constructor的可见性不能比CREATE关键字之后的可见性低。
  5. 父类可见性修改会延伸( extended )到子类

9.2 Implementing the Singleton Pattern(实现单件)

Advantages of Factory Methods(静态方法创建单件的优点):

  • A factory method can have coding that executes before the actual instantiation(提前进行check、读数、锁等功能)
  • Classes can have more than one factory method. Each of the methods can have a different implementation and signature as well(即创建同一个类的不同参数的对象)
  • Use factory methods to administrate the instances of a class within the class itself(管理本类对象)
  • Use a factory method to instantiate one of the subclasses rather than the class itself(先检查type,再根据type创建子类对象)

Factory Method示例代码和Singleton Pattern示例代码:

CLASS lcl_airplane DEFINITION.

CLASS-METHODS:

factory

IMPORTING ...

RETURNING value( ro_plane ) ... .

ENDCLASS.

CLASS lcl_airplane IMPLEMENTATION.

METHOD factory.

READ TABLE gt_instances INTO ro_plane.

IF sy-subrc <> 0.

CREATE OBJECT ro_plane.

APPEND ro_plane TO gt_instances.

ENDIF.

ENDMETHOD.

ENDCLASS.

CLASS lcl_singleton DEFINITION.

CLASS-METHODS:

get_instance

...

RETURNING value( ro_plane ).

ENDCLASS.

CLASS lcl_singleton IMPLEMENTATION.

METHOD get_instance.

IF go_instance IS BOUND.

ro_instance = go_instance.

ELSE.

CREATE OBJECT go_instance.

ro_instance = go_instance.

ENDIF.

ENDMETHOD.

ENDCLASS.

Using the Static Constructor用静态构造函数创建单件:

CLASS lcl_singleton DEFINITION.

CLASS-METHODS:

class_constructor,

get_instance

IMPORTING ...

RETURNING value( ro_instance ).

ENDCLASS.

CLASS lcl_singleton IMPLEMENTATION.

METHOD class_constructor.

CREATE OBJECT go_instance.

ENDMETHOD.

METHOD get_instance.

ro_instance = go_instance.

ENDMETHOD.

ENDCLASS.

  1. 单件,用于防止一个类被实例化多次(游戏引擎就是一种单件)
  2. 静态构造函数在第一次访问一个类时触发(参考2.5节),这里会创建一个单件,而get_instance将返回该单件的引用,这种方法的好处是保证单件必然存在

9.3 Implementing Factory Classes Using Friendship(通过友元实现工厂类)

Friendship Relationships友元关系:

类1:

CLASS lcl_1 DEFINITION

CREATE PRIVATE

FRIENDS lcl_2.

...

PRIVATE SECTION.

DATA a1 ... .

...

ENDCLASS.

类2:

CLASS lcl_2 DEFINITION.

...

ENDCLASS.

CLASS lcl_2 IMPLEMENTATION.

...

METHOD meth.

DATA lo_1 TYPE REF TO lcl_1.

CREATE OBJECT lo_1.

MOVE lo_1->a1 TO ....

ENDMETHOD.

...

ENDCLASS.

  1. lcl_1的创建方式为私有,但是我们需要一个类不考虑lcl_1的实例化可见性而创建lcl_1对象,所以就有了友元类lcl_2
  2. 我们需要在lcl_1的DEFINITION部分用FRIENDS关键字申明友元类lcl_2
  3. 友元关系会被继承,比如lcl_2的子类就可以访问lcl_1的构造函数
  4. 友元类常用于测试私有组件

9.4 Implementing Persistent Objects(实现可持续对象)

普通的ABAP程序数据与对象只在runtime有效,程序结束就会消失。如果想要数据和对象在程序结束后不消失,就需要可持续对象。

创建持久类的方法:

  • 对要持续存在的数据进行建模,建立合适的透明表用于存放持久类的属性,必要时进行数据格式转换
  • SE24创建类,类型选择为持久类( Persistent class ),类名必须以ZCL_为前缀
  • 维护类的属性( attributes ),其中必须有键值(可以是编号或文本)
  • 保存类。系统会自动创建ZCA_<name>作为单件类(学名叫class actor),自动创建ZCB_<name>作为单件类的超类

Creation of Persistent Objects创建可持续对象:

DATA: go_carrier TYPE REF TO zcl_carrier,

go_agent TYPE REF TO zca_carrier.

go_agent = zca_carrier=>agent.

TRY.

go_carrier = go_agent->create_persistent(

i_carrid = 'LH'

i_carrname = 'Lufthansa' ).

CATCH cx_os_object_existing.

...

ENDTRY.

go_carrier->... ."You may save the data to transparent table

COMMIT WORK.

  1. 我们单位首先需要利用class actor获取其静态单件agent(agent的类型即class actor的类型),然后通过agent调用方法create_persistent创建持久类的对象。
  2. 如果要创建的对象,但其键值已经在程序中存在了,agent将抛出cx_os_object_existing异常。
  3. 可持续数据必须通过COMMIT WORK.开始更新,且为异步更新。一旦更新完毕,可续持对象将消失。

Access to Persistent Objects访问可持续对象:

DATA:

go_carrier TYPE REF TO zcl_carrier,

go_agent TYPE REF TO zca_carrier,

gv_carrname TYPE s_carrname.

...

go_agent = zca_carrier=>agent.

TRY.

go_carrier = go_agent->get_persistent( i_carrid = 'LH' ).

gv_carrname = go_carrier->get_carrname().

WRITE: 'LH: ', gv_carrname.

CATCH cx_os_object_not_found.

...

ENDTRY.

  1. 访问数据前同样要先获取静态单件agent,然后调用get_persistent方法访问数据
  2. 如果访问的数据不存在,则报cx_os_object_not_found异常
  3. 注意: 如果我们要获取一组数据,使用的关键字是GET_PERSISTENT_BY_QUERY.以SQL为参数取多条数据。这种方式被称为Query Service

OO Transaction面向对象的事务代码:在transaction maintenance(SE93)中,我们可以创建面向对象的事务代码。这种事务代码可以通过ABAP Objects Services和持久类对象关联,也可以直接和全局类的公有实例方法、本地类的公有实例方法进行关联。当我们调用事务代码时,系统将在internal session创建实例,并调用实例方法。

如果我们勾选了OO Transaction Model,则系统将事务代码和ABAP Objects Service事务代码服务(即持久类)连接。否则,我们可以直接连接全局类或本地类的实例方法。

与全局类连接时,该全局类不能有屏幕定义:

  • constructor不能含有IMPORTING参数
  • 不能有ABAP list displays

创建OO Transaction的步骤:

  1. In the Create Transaction dialog box, enter a description in the Short Text field
  2. Choose the Method of a Class( OO Transaction ) option as an Initial Object
  3. If you want to link the transaction code to a normal instance method, leave the OO Transaction Model checkbox unselected
  4. Enter the Class Name and Method. If you are using a local class, select the Local in Program checkbox and enter the program name
  5. Save the transaction code

事务代码的数据表为TSTC。

Unit10 Program Calls and Memory Management(程序调用与内存管理)

10.1 Calling Programs Synchronously(同步调用程序)

同步调用特点:

  • 主调方暂停,被调方开始执行,被调方执行完毕,主调方恢复运行
  • 被调方获知主调方暂停,即开始执行
  • 同步( Synchronous )调用只能在single user session运行,所以又叫sequential calls
  • 异步( Asynchronous )调用可以并行运行,又叫parallel calls,但必须使用function modules

SUBMIT同步调用代码示例:

SUBMIT prog_name_2.

跳转到prog_name_2,返回时回到调用时的界面(比如在ABAP Editor运行调用程序,最后就返回(BACK) ABAP Editor界面)

SUBMIT prog_name_2 AND RETURN.

跳转到prog_name_2,返回(BACK)时回到SUBMIT语句下一句继续执行

注意: 回到主调程序界面而不是Editor界面,并继续执行

SUBMIT prog_name_2 VIA SELECTION-SCREEN AND RETURN.

跳转到prog_name_2的选择屏,返回(BACK)时回到SUBMIT语句下一句继续执行

DATA set {TYPE|LIKE} RANGE OF {type|dataobject}.

SUBMIT prog_name AND RETURN [ VIA SELECTION-SCREEN ]

WITH parameter {EQ|NE|...} val

WITH sel_opt {EQ|NE|...} val SIGN {'I'|'E'}

WITH sel_opt BETWEEN val1 AND val2 SIGN {'I'|'E'}

WITH sel_opt BETWEEN val1 AND val2 SING {'I'|'E'}

WITH sel_opt IN set.

MODULE user_command_0200 INPUT.

CASE save_ok.

WHEN 'CTRYFR'.

SUBMIT sapbc402_tabd_hashed

WITH pa_ctry = sdyn_conn-countryfr

AND RETURN.

ENDCASE.

ENDMODULE.

完整的SUBMIT写法见左侧,这种方式的优点就是支持select-options的使用。

TRANSACTION同步调用代码示例:

LEAVE TO TRANSACTION 'T_CODE' [AND SKIP FIRST SCREEN].

LEAVE TO这种方式与在command field输入/NT_CODE效果相同,都是终止当前程序进入被调用程序,返回(LEAVE PROGRAM)时回到调用界面

CALL TRANSACTION 'T_CODE' [AND SKIP FIRST SCREEN].

CALL方式,暂停当前程序进入被调用程序,返回(LEAVE PROGRAM)时回到CALL语句下一语句继续执行

AND SKIP FIRST SCREEN允许跳过第一个界面(一般为输入界面)

我们可以用UPDATE关键字选定更新数据的方式,默认为asynchronous,但也可以选择synchronous或local

DATA:

gt_bdcdata TYPE TABLE OF bdcdata,

gs_bdcdata TYPE bdcdata.

* fill gt_bdcdata

...

* call other program

CALL TRANSACTION 'T_CODE' USING gt_bdcdata.

IF sy-subrc = 0.

...

ELSE.

...

ENDIF.

CALL TRANSACTION 'T_CODE' USING gt_bdcdata MODE 'N'.

IF sy-subrc <> 0.

MESSAGE ... WITH sy-subrc.

ENDIF.

BDCDATA: BDC数据,包括要调用的程序、要输入的参数等

MODE: 在CALL TRANSACTION之后,确定屏幕显示方式

  • A: 默认,显示
  • E: 只在有错误时显示
  • N: 不显示

MESSAGE INTO: 在CALL TRANSACTION之后,可存放系统消息到内表中,该内表必须包含BDCMSGCOLL的结构。

SY-SUBRC可以判断调用是否成功。

用内表传输数据的优点:

  • 避免用GET语句填写input fields
  • 后台处理事务代码时,可以直接将事务代码放在内表里

BDCDATA结构:

program

dynpro

dynbegin

fnam

fval

SAPBC402_CALD_CREATE_CUSTOMER

0100

X

   
     

SCUSTOM-NAME

<current_name>

     

SCUSTOM-CITY

<current_city>

     

BDC_OKCODE

SAVE

program: 长度40字符,存放程序名,只能在BDCDATA第一排

dynpro: 长度4字符,存放屏幕编号,只能在BDCDATA第一排

dynbegin: 长度1字符,第一行标识,第一行为X,其它行为空

fnam: 长度132字符,字段名称,N/A即非数字

fval: 长度132字符,字段值,大小写敏感

10.2 Describing the ABAP Runtime and Memory Management(描述ABAP进程与内存管理)

LOAD的定义: when you generatea development, the system creates a runtime object, or LOAD. ( 菜单栏Program->Generate )即运行进程时的程序对象( 只针对Program )

The system generates the LOAD automatically in the following situations:

  • When the program is called and no LOAD exists yet
  • When the LOAD in the database or program buffer is obsolete

A saved or buffered LOAD is considered obsolete under either of the following conditions:

  • The developed version of the program is changed
  • An object that the program uses is changed in the ABAP Dictionary

激活object的步骤:

  • The object is saved, that is, a new inactive version is created. This is subject to a syntax check.
  • The system overwrites the current active version. The previously inactive version is now the active version, which means there is no longer an inactive version.
  • The system creates a new runtime version and updates the LOAD.

程序的可修改部分与不可修改部分:

  • Non-modifiable parts of a program

    (1) Byte code for statements

    (2) Values of constants and literals

    (3) Program texts

    (4) Screen definitions

  • Modifiable parts of a program

    (1) Data objects ( variables )

SAP Memory对应一个User Session和若干External Sessions(即窗口),每个ABAP Memory对应一个External Session和若干Internal Sessions(即程序):

  • External Session ( Main Session )
  1. 每个External Session对应一个SAPGUI window,也对应一个ABAP Memory
  2. 创建External Session可以System->New Session或者/o<t_code>
  3. 最多可以创建16个External Session,但系统默认值为6(设置位置为RZ10->扩展维护->rdisp/max_alt_modes)
  • Internal Session
  1. 一个External Session可包含若干个Internal Session,每个External Session最多包含9个Internal Session且这些Internal Session必须放在同一个call stack
  2. 每调用(可以是SUBMIT、CALL TRANSACTION等任何方式)一个Program,就产生一个Internal Session
  3. 每个Internal Session有一个roll area

每个Internal Session包含多个Program Groups:

  • Main Program Group ( MPG ): 每个Internal Session必然有且只有一个,存放主程序
  • Additional Program Group (APG): 每个主程序调用外部程序时,产生一个APG,故可能存在多个APG
  • 所谓的全局变量,只在一个Program Group有效,即不同的Program Groups中的程序不分享data objects

Call Stack的作用:

  • SUBMIT AND RETURN和CALL TRANSACTION
  1. 当你插入新程序,系统创建新的internal session并放入被调用程序
  2. 新的internal session被放入call stack,旧的internal session仍在原处
  • SUBMIT
  1. 当我们用SUBMIT调用外部程序时,系统创建新的internal session并放入被调用程序
  2. 主调程序的internal session从call stack弹出,且程序中止
  3. 新的internal session被放入call stack,即顶端程序被替换了
  • LEAVE TO TRANSACTION
  1. 当我们用LEAVE TO SCREEN调用外部程序时,系统弹出( destroy )所有call stack中的internal sessions
  2. 系统创建新的internal session并放入被调用程序
  3. 新的internal session被放入call stack
  • CALL FUNCTION和CALL METHOD
  1. 当程序调用尚未载入的function module、global class,则internal session会在MPG之外增加APG
  2. 不同程序调用同一个function group,则每个程序的internal session会存入一个function group且它们相互独立
  3. 当两个Internal Session调用同一个全局类,则它们分别会在APG中创建一个类,两个类相互独立
  4. 当一个Internal Session有多个程序(比如一个主程序和一个函数组)且它们调用了同一个Function,那么该Function只载入一次(当两个程序使用同一个静态属性时是有风险的,请用实例属性代替静态属性)
  5. 当主程序调用外部subroutine,系统不会再创建Internal Session,而是在主程序的MPG中载入subroutine

同一个program group的程序,要遵循以下限制:

  • 它们共享全局的声明,比如TABLES、NODES、COMMON PART
  • CALL SCREEN只能用于调用主程序的屏幕

不同的program groups的程序,或者不同的Internal Sessions或External Sessions的程序,它们不能直接共享数据,而是需要数据交换:

  • Using the interface of the called program ( screen or selection screen )
  • ABAP memory(用于同一个窗口的不同程序传数):
  1. 用于同一个用户下同一个External Session
  2. 可存放任意ABAP Data Objects(比如字段、结构、内表、复杂对象)
  3. 能用于同一个External Session内部的Internal Session传数
  4. 当用户退出窗口(比如命令栏输入/I),则ABAP memory清空
  • SAP memory(用于同一个用户不同窗口的传数):
  1. 用于同一个用户( user-specific )的不同External Sessions传数
  2. 存放字段数据,可用于提供屏幕默认值
  3. 不能用于Internal Sessions之间传数
  4. 其数据持续时间与用户登录时间一致
  • Database tables
  • Local files on the presentation server

ABAP memory读写代码示例:

PROGRAM p1 ... .

DATA: gs_spfli TYPE spfli,

gt_fli TYPE STANDARD TABLE OF sflight.

EXPORT conn = gs_spfli

fights = gt_fli

TO MEMORY ID 'MY_ID'.

PROGRAM p2 ... .

DATA: gs_conn TYPE spfli

gt_data TYPE STANDARD TABLE OF sflight.

IMPORT conn = gs_conn

flights = gt_data

FROM MEMORY ID 'MY_ID'.

FREE MEMORY ID 'MY_ID'.

  1. EXPORT和IMPORT参数顺序必须一致
  2. 显式释放ABAP memory的方法是FREE MEMORY,如果不指定ID,将清空全部的ABAP memory

SAP memory读写代码示例:

PROGRAM p1 ... .

SELECTION-SCREEN BEGIN OF LINE.

SELECT-OPTIONS: s_gjahr FOR bkpf-gjahr MEMORY ID GJR.

SELECTION-SCREEN END OF LINE.

PROGRAM p2 ... .

SET PARAMETER ID 'GJR' FIELD sy-datum+0(4).

CALL TRANSACTION 'BC402_TABD_SORT'.

PROGRAM p3 ... .

GET PARAMETER ID 'GJR' FIELD lv_gjahr.

  1. 选择屏幕通过MEMORY ID设置SAP memory id
  2. 对话框屏幕通过Parameter ID属性设置SAP memory id
  3. ABAP Dictionary可以设置字段的Parameter ID
  4. SU3也可以设置字段的Parameter ID
  5. SET PARAMETER ID指向字段赋值
  6. GET PARAMETER ID指从字段读值

扁平数据( Flat Data Objects ): elementary data objects with fixed length and structures that only have flat components.

扁平数据开辟内存的方式: 直接开辟内存

扁平数据释放内存的方式: 遇到CLEAR或FREE时清空内存区,只有等到internal session结束时才释放内存区

字符串( Strings ): 就是字符串,字符串属于深度数据对象( deep data objects ),也属于动态数据对象( dynamic data objects )

字符串开辟内存的方式: 先开辟8字节的引用,再开辟大约100字节的Header(与硬件有关),最后开辟可扩展的Data区(String可用内存区最大为2GB,用ztta/max_memreq_MB配置)

字符串释放内存的方式: 遇到CLEAR或FREE时清空Data区,但保留引用区和Header区

内表( Internal Table ): 与字符串类似,属于深度数据对象和动态数据对象

内表开辟内存的方法: 先开辟8字节引用,再开辟Header区,内表的Header区比较大,因为内表数据分布于不同的Data区,而Header区会存放这些Data区的地址(初次开辟内存时,可以用INITIAL SIZE指定内存大小)

内表释放内存的方法: 遇到CLEAR和REFRESH会只清空Data区,遇到FREE会清空Data区和Header区

Memory Optimization when Copying Dynamic Data Objects(动态数据赋值的内存优化):

DATA: gv_string1 TYPE string,

gv_string2 TYPE string.

gv_string1 = 'abcdef'.

gv_string2 = gv_string1.

CONCATENATE gv_string1 'ghi' INTO gv_string1.

当gv_string1给gv_string2赋值时,因为值相等,它们的引用会指向同一个内存区(共享Header区),以节省内存。

当gv_string1的值不再与gv_string2的值相等时,则会开辟新内存区存放gv_string2的值。

DATA: gt_itab0 TYPE TABLE OF ... .

gt_itab1 LIKE gt_itab0.

SELECT * FROM ... INTO TABLE gt_itab0 WHERE ... .

gt_itab1 = gt_itab0.

APPEND ... TO gt_itab0.

当gt_itab0给gt_itab1赋值时,因为值相等,它们的引用会指向同一个内存区(但分别有自己的Header区),以节省内存。

当内表数据不再一致时,则开辟新内存区存放gt_itab1的值。

10.3 Using Shared Objects(使用共享对象)

SHARED BUFFER和SHARED MEMORY使用代码示例:

DATA: matnr TYPE matnr.

matnr = ' 000000000000123456'.

EXPORT matnr TO SHARED BUFFER indx(aa) ID 'YTEST_MATNR'.

IMPORT matnr FROM SHARED BUFFER indx(aa) ID 'YTEST_MATNR'.

1. Shared Memory(共享内存): is a memory area on an application server that the ABAP programs running on the same server can access.(在Shared Objects诞生前,ABAP使用的就是SHARED BUFFER和SHARED MEMORY,同一个服务器的程序可以共享数据。)

2. 共享对象只有服务器关闭时才会清空,也可用FREE SHARED BUFFER这样的命令强制清空。

哪些情况适合使用Shared Objects:

  • Should be used for cross-program buffering of data that is read often, but rarely written.
  • Concurrentread(并发访问) accesses are supported by shared objects.
  • Access is controlled by a lock mechanism.
  • Data is saved as attributes of objects.
  • Memory bottlenecks result in runtime errors and have to be caught.

创建共享对象( shared objects )的过程:

  1. 定义操作类( global class for catalog data ):
    1. SE24,创建操作类,比如命名为ZCL_CATALOG,然后选择共享内存启用( Shared Memory-Enabled )
    2. 在类型定义中,定义数据类型(比如TY_CATALOG),共享对象要操作的数据对象一般为内表,需要专门定义
    3. 在属性定义中,用内表类型定义属性(比如CATALOG)
    4. 编写读数和写数的方法
  2. 定义根类( area root class ):
    1. SE24,创建根类,比如命名为ZCL_ROOT,然后选择共享内存启用( Shared Memory-Enabled )
    2. 根类中只有属性没有方法,属性类型与操作类相同
  3. 定义共享区域( area in shared memory )
    1. SHMA中定义共享区域,比如ZCL_AREA。勾选特定客户端区域( Client-Specific Area ),即只对特定clinet使用。勾选带版本控制( With Versioning ),即激活版本被访问时,用户可以进行后续修改增加新版本
    2. 根类参数设置为前面定义的根类,比如ZCL_ROOT
    3. 系统随后会自动创建类ZCL_AREA,该类带有读写数据的方法

使用共享对象( shared objects )的代码示例:

.

p_from = wa_flights-cityfrom.
        p_to   = wa_flights-cityto.
        p_date1 = wa_flights-fldate.
        p_date2 = wa_flights-fldate.

ENDIF.

ENDIF.

ELSE.
    LEAVE PROGRAM.
  ENDIF.

START-OF-SELECTION.

"------------------------------
  " 写数示例
  "------------------------------
  go_handle = zcl_area=>attach_for_write( ).

CREATE OBJECT go_root AREA HANDLE go_handle.
  CREATE OBJECT go_catalog AREA HANDLE go_handle.

go_root->mo_catalog = go_catalog.
  go_handle->set_root( root = go_root ).

go_handle->root->mo_catalog->fill_catalog(
    iv_catalog = gt_flights
  ).

go_handle->detach_commit( ).

"------------------------------
  " 读数示例
  "------------------------------
  go_handle = zcl_area=>attach_for_read( ).

DATA:
    lv_from     TYPE fl_from,
    lv_to       TYPE fl_to,
    lv_earliest TYPE date,
    lv_latest   TYPE date.

lv_from = p_from.
  lv_to = p_to.
  lv_earliest = p_date1.
  lv_latest = p_date2.

go_handle->root->mo_catalog->get_flights(
    EXPORTING
      iv_from_city = lv_from
      iv_to_city = lv_to
      iv_earliest = lv_earliest
      iv_latest = lv_latest
    IMPORTING
      et_flight = gt_flights2
  ).

go_handle->detach( ).

BREAK-POINT.

  1. 当我们用ATTACH_FOR_WRITE这种静态方法实例化go_handle时,系统会在shared memory开辟一个内存区存放area class的实例,即shared objects。
  2. 在有了shared objects以后,通过AREA HANDLE关键字指定在哪个共享区域创建根类对象和操作类对象。
  3. 绑定根类对象和操作类对象,绑定共享对象和根类对象。
  4. 在写数( write )过程中,用户不能读数,直到解锁。解除写锁( write lock )的方法即DETACH_COMMIT。该方法继承自区域类CL_SHM_AREA。
  5. 当读数( read )结束后,需要解除读锁( read lock ),方法即DETACH
  6. 在修改共享区域时,请确认系统是否允许多个版本存在。对于写数而言,如果我们修改了版本,则老版本解锁(DETACH_COMMIT)之后,系统会激活新版本。旧的版本的attribute将被设置为OUTDATED。

    对于读数而言,读锁不影响版本更新,所以修改期间各个用户看到的数据可能不同。不过解锁后,旧版本将过期( expired ),之后的读锁将锁住新版本。

Unit11 Dynamic Programming(动态编程)

11.1 Explaining the Dynamic Programming Techniques of ABAP(解释ABAP动态编程技术)

ABAP支持以下类型动态编程:

  • Dynamic extension of internal tables and strings
  • Dynamic offset and length specification ( in MOVE, WRITE TO, formal parameters, and field symbols )
  • Dynamic specification of program object names( the name of a table, modularization unit, sort criterion, control level criterion, and so on )
  • Dynamic specification of type and data declarations

常用的动态编程技术包括:

  • Dynamic language elements(比如动态生成函数名称字符串,再用CALL FUNCTION进行调用)
  • Generating programs from within programs(可将代码保存进内表,然后运行时再读出)
  • Field symbols(即指针技术)
  • Runtime type services(RTTI,动态生成类型与数据对象)

11.2 Using Dynamic Statements and Dynamic Calls(动态声明与动态调用)

Dynamic Tokens in ABAP Statements(动态ABAP声明的参数代码对比):

静态

动态

DO 5 TIMES.

...

ENDDO.

gv_int = 5.

DO gv_int TIMES.

...

ENDDO.

WRITE gv_string+2(5).

gv_len = 5.

gv_off = 2.

WRITE gv_string+gv_off(gv_len).

MESSAGE e038(BC402).

MESSAGE ID 'BC402' TYPE 'E' NUMBER '038'.

gv_msgid = 'BC402'.

gv_msgty = 'E'.

gv_msgno = '038'.

MESSAGE ID gv_msgid TYPE gv_msgty NUMBER gv_msgno.

SORT gt_spfli TY cityfrom.

gv_comp = 'CITYFROM'.

SORT gt_spfli BY (gv_comp).

READ TABLE gt_spfli INTO gs_spfli

WITH KEY carrid = 'LH'

Connid = '0400'.

gv_nam1 = 'CARRID'.

gv_val1 = 'LH'.

gv_name2 = 'CONNID'.

gv_val2 = '0400'.

READ TABLE gt_spfli INTO gs_spfli

WITH KEY (gv_nam1) = (gv_val1)

(gv_nam2) = (gv_val2).

可能报错:

如果超过字符串长度,会报DATA_OFFSET_LENGTH_TOO_LARGE runtime错误

或CX_SY_RANGE_OUT_OF_BOUNDS异常类

Dynamic Open SQL(动态OPEN SQL示例):

动态FROM

gv_tabname = 'SPFLI'.

SELECT COUNT(*) FROM (gv_tabname) INTO gv_int.

动态FIELDS

gv_fieldlist = 'CARRID CONNID CITYFROM CITYTO'.

SELECT (gv_fieldlist) FROM spfli INTO CORRESPONDING FIELDS OF TABLE gt_spfli.

动态WHERE(单条)

gv_cond = 'CARRID = 'LH' AND CONNID = '0400' '.

SELECT * FROM spfli INTO TABLE gt_spfli WHERE (gv_cond).

动态WHERE(多条)

APPEND 'CARRID = 'LH' AND ' TO gt_cond.

APPEND 'CONNID > '0400' ' TO gt_cond.

SELECT * FROM spfli INTO TABLE gt_spfli WHERE (gv_cond).

完整展示

REPORT ZTEST32.

DATA:
  LV_FIELD TYPE STRING, "动态字段列表
  LV_TABLE TYPE STRING, "动态数据库表名
  LV_WHERE TYPE STRING, "动态筛选条件
  LV_GROUPBY TYPE STRING, "动态分组
  LV_HAVING  TYPE STRING, "动态分组筛选条件
  LV_ORDERBY TYPE STRING, "动态排序
  LV_ROWS  TYPE i. "动态行数

FIELD-SYMBOLS:
  <FS_TABLE> TYPE STANDARD TABLE.

SELECT (LV_FIELD)
  FROM (LV_TABLE)
  INTO CORRESPONDING FIELDS OF TABLE @<FS_TABLE> "除了INTO语句需要指针,其它均可直接使用动态编程
    UP TO @LV_ROWS ROWS
  WHERE (LV_WHERE)
    GROUP BY (LV_GROUPBY)
    HAVING (LV_HAVING)
    ORDER BY (LV_ORDERBY).

可能报错:

CX_SY_DYNAMIC_OSQL_SEMANTICS, CX_SY_DYNAMIC_OSQL_SYNTAX

Dynamic Calls(动态调用代码):

调用子例程

PERFORM (gv_form_name) IN PROGRAM (gv_prog_name).

调用FM

CALL FUNCTION gv_func_name … .

调用方法

CALL METHOD go_instance->(gv_meth_name) … .

CALL METHOD (gv_class_name)=>(gv_static_meth_name) … .

执行程序

SUBMIT (gv_report_name).

调用事务代码

CALL TRANSACTION gv_transaction_code … .

LEAVE TO TRANSACTION gv_transaction_code … .

用参数表调用FM

TYPE-POOLS: abap.

DATA: gt_param TYPE abap_func_parmbind_tab,

gt_excp TYPE abap_func_excepbind_tab.

gv_func_name = 'Z_MY_FUNCTION'.

*fill the gt_param and gt_excep

CALL FUNCTION gv_func_name

PARAMETER-TABLE gt_parm

EXCEPTION-TABLE gt_excp.

IF sy-subrc <> 0.

ENDIF.

ABAP_FUNC_PARMBIND_TAB有三个字段:

  1. NAME: 参数名称
  2. KIND: 参数类型(整型参数,可填入ABAP_FUNC_EXPORTING,ABAP_FUNC_IMPORTING等值,代表exporting,importing,changing,tables等参数类型)
  3. VALUE: 值,该字段类型为TYPE REF TO DATA,如果要填入值可用以下方法GET REFERENCE OF act_par INTO gs_partab-value.

用参数表调用方法

TYPE-POOLS: abap.

DATA: gt_parm TYPE abap_parmbind_tab,

gt_excp TYPE abap_excpbind_tab.

gv_method_name = 'MY_METHOD'.

*fill the gt_parm and gt_excp

CALL METHOD ref->(gv_method_name)

PARAMETER-TABLE gt_parm

EXCEPTION-TABLE gt_excp.

与参数表调用FM存在区别:

  1. 参数表类型为ABAP_PARMBIND_TAB和ABAP_EXCPBIND_TAB
  2. ABAP_PARMBIND_TAB是哈希表而不是排序表,不能用index访问
  3. KIND值可为空,要用代码填写KIND值需借助CL_ABAP_OBJECTDESCR,而不是类型池ABAP

用参数表调用程序

DATA: gt_sel TYPE TABLE OF rsparams.

gv_prog_name = 'MY_REPORT'.

*fill gt_sel

SUBMIT (gv_prog_name)

WITH SELECTION-TABLE gt_sel.

RSPARAMS包含以下字段:

  1. SEL_NAME: 参数名称,必须为大写
  2. KIND: 参数类型,P为字段参数,S为范围参数
  3. SIGN, OPTION, LOW, HIGH: 如果类型为S,需要像设置范围那样设置这些字段;如果类型为P,只填写LOW字段

可能报错:

CX_SY_DYN_CALL_ERROR或其它CX_SY_DYN_CALL_开头的异常类(前者的子类)

Persistent Program Generation(动态生成程序):

DATA gt_tab TYPE TABLE OF string.

DATA gv_prg TYPE program.

*fill internal table

APPEND 'REPORT ztest.' TO gt_tab.

APPEND 'PARAMETERS pa TYPE s_carr_id.' TO gt_tab.

APPEND 'START-OF-SELECTION.' TO gt_tab.

APPEND 'WRITE ''Hello''.' TO gt_tab.

gv_prg = 'ZTEST'.

INSERT REPORT gv_prg FROM gt_tab.

SUBMIT (gv_prg) VIA SELECTION-SCREEN AND RETURN.

DATA gt_tab TYPE TABLE OF string.

DATA gv_prg TYPE program.

*fill internal table

APPEND 'REPORT ztest.' TO gt_tab.

APPEND 'FORM show.' TO gt_tab.

APPEND 'WRITE ''Hello''.' TO gt_tab.

APPEND 'ENDFORM.' TO gt_tab.

*create S type report

GENERATE SUBROUTINE POOL gt_tab NAME gv_prg.

PERFORM show IN PROGRAM (gv_prg) IF FOUND.

11.3 Using Generic Data Types(使用通用数据类型)

ABAP Built-In Generic Data Types(ABAP预定义通用数据类型):

 

Generic Data Type

Meaning

Compatible ABAP Types

Fully gneric

any

Any type

Any

data

Any data type

Any

Elementary, flat

simple

Elementary data type or flat, character-like structure

Any elementary type and any flat, character-like structure

Numeric

numeric

Numeric data type

i, f, p, int8, decfloat16, decfloat34

decfloat

Decimal floating point

decfloat16, decfloat34

Character-type

clike

Character data type

c, n, d, t, string

csequence

Text data type

c, string

Hexadecimal

xsequence

Byte data type

x, xstring

Typing for reference variables(引用通用类型):

TYPE REF TO OBJECT

Reference variables

TYPE REF TO DATA

Reference variables

Generic ABAP Data Types – Internal Tables(内表通用类型):

Generic Data Type

Meaning

Compatible ABAP Types

any table

Any internal table

Any table type

index table

Any index table

Standard or sorted

standard table

Any standard table

Only standard

sorted table

Any sorted table

Only sorted

hashed table

Any hashed table

Only hashed

代码示例:

TYPES:

gty_t_gen1 TYPE ANY TABLE OF spfli, "ANY TABLE不指定key

gty_t_gen2 TYPE SORTED TABLE OF spfli WITH KEY carrid connid. "SORTED TABLE可指定unique-key或ono-unique key

gty_t_gen3 TYPE INDEX TABLE OF spfli WITH DEFAULT KEY. "SORTED TABLE或STANDARD TABLE可用默认key

动态参数的错误写法与正确写法对比:

CLASS-METHODS write_any_table

IMPORTING

ig_info TYPE ANY

it_data TYPE ANY.

METHOD write_any_table.

FIELD-SYMBOLS <ls_line> TYPE ANY.

"Syntax error,因为ANY不能确定是否为表

LOOP AT it_data ASSIGNING <ls_line>.

ENDLOOP.

"Possible runtime error,因为ANY不能确定是否能WRITE

WRITE / ig_info.

ENDMETHOD.

CLASS-METHODS write_any_table

IMPORTING

ig_info TYPE SIMPLE

it_data TYPE ANY TABLE.

METHOD write_any_table.

FIELD-SYMBOLS <ls_line> TYPE ANY.

"正确,ANY TABLE必然为表

LOOP AT it_data ASSIGNING <ls_line>.

ENDLOOP.

"正确,SIMPLE类型必然可以WRITE

WRITE / ig_info.

ENDMETHOD.

Generically Typed Field Symbols(通用指针类型):        Type Casting With Field Symbols(通过指针进行类型转换):

DATA: gt_scarr TYPE TABLE OF scar,

gt_sbook TYPE TABLE OF sbook.

FIELD-SYMBOLS:

<fs_tab> TYPE ANY TABLE. "可指向任意类型内表

CASE gv_table_name.

WHEN 'SCARR'.

ASSIGN gt_scarr TO <fs_tab>.

WHEN 'SBOOK'.

ASSIGN gt_sbook TO <fs_tab>.

ENDCASE.

IF <fs_tab> IS ASSIGNED.

SELECT * FROM (gv_table_name)

UP TO 100 ROWS

INTO TABLE <fs_tab>.

ENDIF.

TYPES: BEGIN OF gty_s_date,

year TYPE n LENGTH 4,

month TYPE n LENGTH 2,

day TYPE n LENGTH 2,

END OF gty_s_date.

*option1: implicit隐式,指明fs类型,不指明casting类型

FIELD-SYMBOLS <fs> TYPE gty_s_date.

ASSIGN sy-datum TO <fs> CASTING.

WRITE: / <fs>-year, <fs>-month, <fs>-day.

*option2: explicit显式,不指明fs类型,指明casting类型

FIELD-SYMBOLS: <fs> TYPE ANY.

ASSIGN sy-datum TO <fs> CASTING TYPE gty_s_date.

Dynamic Access to Data Objects(用指针访问不同类型变量):

Any data object

gv_name = 'GV_CARRID'.

ASSIGN (gv_name) TO <fs>.

Structure component ( full name )

gv_name = 'LS_SPFLI-CARRID'. "即可以不用COMPONENT获取字段

ASSIGN (gv_name) TO <fs>.

Structure component ( component name )

gv_comp_name = 'CARRID'.

ASSIGN COMPONENT gv_comp_name OF STRUCTURE gs_spfli TO <fs>.

Structure component ( position )

gv_comp_number = 2.

ASSIGN COMPONENT gv_comp_number OF STRUCTURE gs_spfli TO <fs>.

Static attribute ( full name )

gv_name = 'LCL_VEHICLE=>N_O_AIRPLANES'.

ASSIGN (gv_name) TO <fs>.

Static attribute ( part name )

gv_attribute_name = 'N_O_AIRPLANES'.

ASSIGN (gv_class_name)=>(gv_attribute_name) TO <fs>.

Instance attribute ( full name )

gv_name = 'LO_VEHICLE->N_O_AIRPLANES'.

ASSIGN (gv_name) TO <fs>.

Instance attribute ( part name )

gv_attribute_name = 'MAKE'.

ASSIGN go_vehicle->(gv_attribute_name) TO <fs>.

Full Processing of Any Non-Nested, Flat Structure(处理非嵌套结构的完整代码示例):

CLASS-METHODS: write_any_struct

IMPORTING is_struct TYPE any.

METHOD write_any_struct.

FIELD-SYMBOLS: <ls_comp> TYPE simple.

DO.

ASSIGN COMPONENT sy-index OF STRUCTURE is_struct TO <ls_comp>. "可以用index也可以用name

IF sy-subrc <> 0.

EXIT.

ELSE.

WRITE <ls_comp>.

ENDIF.

ENDMETHOD.

Cast Assignment for Data References(数据引用的类型转换):

DATA:

gv_int TYPE i VALUE 15,

gv_date TYPE d VALUE '20040101'.

DATA:

gr_int TYPE REF TO i,

gr_date TYPE REF TO d,

gr_gen TYPE REF TO data.

GET REFERENCE OF gv_int INTO gr_int.

*up cast

gr_gen = gr_int.

*down cast

gr_int ?= gr_gen.

*runtime error:

GET REFERENCE OF gv_date INTO gr_date.

*up cast

gr_gen = gr_date.

*down cast

gr_int ?= gr_gen.

  1. 引用可被视为指针,i型引用就是指向i型数据的指针,GET REFERENCE OF gv_int INTO gr_int.本质上就是把gv_int的地址赋值给了gr_int.
  2. 通用类型data比i范围更大,所以data型引用指向i型引用是up cast,而反之则是down cast
  3. gr_date本身是d型引用,如果gr_gen获取了gr_date的地址然后down cast到gr_int,就会发生CX_SY_MOVE_CAST_ERROR错误

Defreferenced Generic Data References(解引用):

DATA gr_data TYPE REF TO DATA.

... "Fill the generic data reference

ASSIGN gr_data->* TO <fs>.

  1. 通用类型解引用只能通过ASSIGN关键字
  2. 解引用后,指针(<fs>)与引用指向同一个地址
  3. 该地址即为数据的地址

11.4 Describing Data Types, Data Objects, and Objects at Runtime(描述数据类型、数据对象、进程对象)

在RTTI ( runtime type identification )诞生前,ABAP描述数据的关键字有DESCRIBE FIELD和DESCRIBE TABLE。

Only six of the ten RTTI classes can be instantiated and used to describe specific types(只有六种可实例化的RTTI类):

RTTI Class

Purpose

CL_ABAP_ELEMDESCR

To describe elementary data types

CL_ABAP_REFDESCR

To describe reference types

CL_ABAP_STRUCTDESCR

To describe structure types

CL_ABAP_TABLEDESCR

To describe table types

CL_ABAP_CLASSDESCR

To describe classes

CL_ABAP_INTFDESCR

To describe interfaces

其实还有四种虚类作为上述类的超类,它们不能实例化但是可以定义引用类型,具体继承关系请看TAW12-1 590页。

获取类型的属性:

通过Name获取类型属性

DATA go_type TYPE REF TO cl_abap_typedescr.

*Analysis of a local data type

TYPES gty_type TYPE ... .

go_type = cl_abap_typedescr=>describe_by_name( 'GTY_TYPE' ).

*Analysis of a global data type

go_type = cl_abap_typedescr=>describe_by_name( 'SPFLI' ).

*Analysis of a local object type

CLASS lcl_class DEFINITION.

...

ENDCLASS.

go_type = cl_abap_typedescr=>describe_by_name( 'LCL_CLASS' ).

*Analysis of a global object type

go_type = cl_abap_typedescr=>describe_by_name( 'IF_PARTNERS' ).

通过引用或者对象获取类型属性

*Analysis of a generic parameter

… IMPORTING ig_data_object TYPE any …

go_type = cl_abap_typedescr=>describe_by_data( ig_data_object ).

*Analysis of a reference variable ( object reference )

DATA: go_vehicle TYPE REF TO lcl_vehicle.

go_type = cl_abap_typedescr=>describe_by_data( go_vehicle )."获取引用的类型参数

go_type = cl_abap_typedescr=>describe_by_object_ref( go_vehicle )."获取引用指向的对象的类型参数

注意:获取类型属性的方法

  • DESCRIBE_BY_NAME: 当我们知道类型名称时,可用该静态方法获取类型的属性,如果找不到该类型则报TYPE_NOT_FOUND普通异常
  • DESCRIBE_BY_DATA: 当我们知道某个数据对象时,可用该静态方法获取类型的属性,如果数据对象是通用类型,则返回其实际类型的描述
  • DESCRIBE_BY_DATA_REF: 当我们知道某个数据的引用时,可用该静态方法获取类型的属性,如果引用未实例化则报REFERENCE_IS_INITIAL
  • DESCRIBE_BY_OBJECT_REF: 当我们知道某个实例时,可用该静态方法获取类型的属性,如果引用未实例化,则报REFERENCE_IS_INITIAL

Casting a Suitable Reference for a Type Description Object(通过描述对象进行引用类型转换):

DATA: go_type TYPE REF TO cl_abap_trypedescr,

go_elem TYPE REF TO cl_abap_elemdescr,

go_ref TYPE REF TO cl_abap_refdescr,

go_struct TYPE REF TO cl_abap_structdescr,

go_table TYPE REF TO cl_abap_tabledescr,

go_intf TYPE REF TO cl_abap_intfdescr,

go_class TYPE REF TO cl_abap_classdescr.

go_type = cl_abap_typedescr=>describe_by_ … .

CASE go_type->kind.

WHEN cl_abap_typedescr=>kind_elem.

go_elem ? = go_type.

WHEN cl_abap_typedescr=>kind_ref.

go_ref ?= go_type.

WHEN cl_abap_typedescr=>kind_struct.

go_struct ?= go_type.

WHEN cl_abap_typedescr=>kind_table.

go_table ?= go_type.

WHEN cl_abap_typedescr=>kind_intf.

go_intf ?= go_type.

WHEN cl_abap_typedescr=>kind_class.

go_class ?= go_type.

ENDCASE.

Elementary Data Type Analysis(基本类型数据的类型属性分析):

DATA: gv_class TYPE sbook-class,

go_elem TYPE REF TO cl_abap_elemdescr,

go_dfies TYPE dfies. "该结构有单个字段的全部属性

go_elem ?= cl_abap_typedescr=>describe_by_data( gv_class ).

IF go_elem->is_ddic_type( ) = cl_abap_typedescr=>true.

gs_dfies = go_elem->get_ddic_field(

EXPORTING p_langu = sy-langu "有些字段描述跟语言有关,就需要设置语言

).

ENDIF.

Reference Type Analysis(引用型数据的类型属性分析):

DATA: gr_spfli TYPE REF TO spfli,

go_ref TYPE REF TO cl_abap_refdescr,

go_target TYPE REF TO cl_abap_typedescr,

go_struct TYPE REF TO cl_abap_structdescr.

go_ref ?= cl_abap_typedescr=>describe_by_data( gr_spfli )."获取引用的属性

"获取static type即引用定义的类型,如果为structure引用,那么返回的值即CL_ABAP_STRUCTDESCR对象,如果为class或interface引用,那么返回的值为CL_ABAP_CLASSDESCR或CL_ABAP_INTFDESCR对象

go_target = go_ref->get_referenced_type( ).

IF go_target->kind = cl_abap_typedescr=>kind_struct.

go_struct ?= go_target.

ENDIF.

Structure Type Analysis(结构数据的类型属性分析):

DATA: gs_spfli TYPE spfli,

go_struct TYPE REF TO cl_abap_structdescr,

gs_comp TYPE abap_componentdescr. "字段描述器

go_struct ?= cl_abap_typedescr=>describe_by_data( ls_spfli ).

"如果我们要一次性不循环取出所有字段的属性,应该使用GET_DDIC_FIELD_LIST取出列表

"如果只取一个字段的属性,应该使用GET_DDIC_FIELD取出一个结构

LOOP AT go_struct->components INTO gs_comp.

WRITE: / gs_comp-name,

gs_comp-type_kind,

gs_comp-length,

gs_comp-deimals.

ENDLOOP.

Navigation from Structure Type to Component Types(通过结构去做字段的类型属性分析):

*Type description of a specific component

DATA: go_struct TYPE REF TO cl_abap_structdescr,

go_comp TYPE REF TO cl_abap_datadescr.

*fill go_struct with reference to structure type

go_comp = go_struct->get_component_type('CARRID' ).

*Type description of all components

DATA: go_struct TYPE REF TO cl_abap_structdescr,

go_elem TYPE REF TO cl_abap_elemdescr,

gt_comp TYPE cl_abap_structdescr=>component_table,

gs_comp TYPE cl_abap_structdescr=>component.

*fill go_struct with reference to structure type

gt_comp = go_struct->get_components( ).

LOOP AT gt_comp INTO gs_comp.

IF gs_comp-type->kind = cl_abap_typedescr=>kind_elem.

go_elem ?= gs_comp-type.

ENDIF.

ENDLOOP.

"注意: 考虑到components的类型各种各样,我们可以用REF TO CL_ABAP_DATADESCR的引用获取components的类型属性,再通过downcast转换成REF TO CL_ABAP_ELEMDESCR这样的子类型。

Table Type Analysis(表数据的类型属性分析):

DATA: gt_spfli TYPE spfli_tab,

go_table TYPE REF TO cl_abap_tabledescr,

go_line TYPE REF TO cl_abap_datadescr,"我们用该类型存放每行的类型属性

go_struct TYPE REF TO cl_abap_structdescr.

go_table ?= cl_abap_typedescr=>describe_by_data( gt_spfli ).

go_line = go_table->get_table_line_type( ). "获取每行的类型属性

IF go_line->kind = cl_abap_typedescr=>kind_struct.

go_struct ?= go_line.

ENDIF.

Object Type Analysis(对象数据的类型属性分析):

DATA: go_class TYPE REF TO cl_abap_classdescr,"有时也用cl_abap_intfdescr

gs_methdescr TYPE abap_methdescr,

gs_parmdescr TYPE abap_parmdescr.

go_class ?= cl_abap_typedescr=>describe_by_name( 'CL_RENTAL' ).

"除了methods,还有attributes、events、interfaces等属性,它们都是内表

"class_kind属性允许我们查看是否为abstract、final等类型,而cl_abap_intfdescr则对应的是intf_kind属性

READ TABLE go_class->methods INTO gs_metdescr WITH KEY name = 'CONSTRUCTOR'.

LOOP AT gs_metdescr-parameters INTO gs_parmdescr.

WRITE: / gs_parmdescr-name,

gs_parmdescr-type_kind,

gs_parmdescr-length.

ENDLOOP.

"注意:GET_ATTRIBUTE_TYPE提供属性描述,GET_METHOD_PARAMTER_TYPE提供参数描述,GET_SUPER_CLASS_TYPE提供父类描述

11.5 Creating Data Types, Data Objects, and Objects at Runtime(在运行时创建数据类型、数据对象与类对象)

动态的类、内表和SQL语言:

DATA: gv_tabname TYPE string,

gv_max_rows TYPE i,

gr_table TYPE REF TO data.

FIELD-SYMBOLS: <gt_table> TYPE ANY TABLE.

gv_tabname = 'SPFLI'.

gv_max_rows = 100.

CREATE DATA gr_table TYPE TABLE OF (gv_tabname).

ASSIGN gr_table->* TO <gt_table>.

SELECT * FROM (gv_tabname) UP TO gv_max_rows ROWS

INTO TABLE <gt_table>.

用HANDLE关键字通过descriptor创建引用对象:

DATA: gv_tabname TYPE string,

gv_max_rows TYPE i,

go_struct TYPE REF TO cl_abap_structdescr,

go_table TYPE REF TO cl_abap_tabledescr,

gr_table TYPE REF TO data.

FIELD-SYMBOLS: <gt_table> TYPE ANY TABLE.

gv_tabname = 'SPFLI'.

gv_max_rows = 100.

go_struct ?= cl_abap_typedescr=>describe_by_name(

p_name = gv_tabname ).

go_table = cl_abap_tabledescr=>create( "创建内表型descriptor对象

p_line_type = go_stuct ).

CREATE DATA gr_table TYPE HANDLE go_table. "用descriptor对象创建数据引用对象

ASSIGN gr_table->* TO <gt_table>.

SELECT * FROM (gv_tabname)

UP TO gv_max_rows ROWS

INTO TABLE <gt_table>.

上一篇:如何让Sublime Text编辑器支持新的ABAP关键字


下一篇:关于php内存释放问题 内存溢出问题(二)