面向对象
类CLASS
类分为局部类与全局类。全局类是通过ABAP工作中的类创建工具 Class Bulider 开发的(事务码为 SE24),主要保存在Class pool系统类库中,可供所有ABAP程序使用;而在程序内部(如报表程序内)定义的类则一般只应用于本程序,除了使用范围不同外,其它都是一样的;在使用类时系统首先会寻找程序内部定义的局部类,不存在时才去寻找全局类。
ABAP中的类定义是由类的声明与类的实现两部分组成
声明DEFINITION
CLASS class DEFINITION [class_options].
[PUBLICSECTION.
[components]]
[PROTECTEDSECTION.
[components]]
[PRIVATESECTION.
[components]]
ENDCLASS.
类的所有成员变量和成员方法都必须在“DEFINITION”部分进行声明,在声明时需指定成员的可见性。类的属性成员在类的声明部分定义,而类的方法则需要在类的声明和实现两部分才能完成定义:在声明部分说明方法的参数接口,在实现部分则通过ABAP代码完成具体功能。
类的方法需要在类的声明和实现两部分都要进行定义,在类的声明部分说明方法的参数接口,在类的实现部分则说明了具体的功能。
类的成员有三种可见范围:公共的、保护的、私有的:
o 公共部分PUBLIC:公共部分定义的类成员可以被所有的外部对象使用,包括类的方法及其派生类中定义的方法,公共部分的成员(特别是方法)构成了类对外接口
o 保护部分PROTECTED:保护部分定义的类成员只能被类及其派生类中的方法使用,对其他类或程序不可见了
o 私有部分PRIVATE:私有部分定义的类成员只能被该类自身其他成员所访问
一般不在类的公有部分定义类的属性,因为公有的可以直接被外界修改,违背了面向对象的封装原则。如果要将数据定义公共区,则一般是常量,不需要修改的,如圆周率。所以类的属性一般定义在私有或保护区
class_options
...[PUBLIC]
[INHERITING FROM superclass]
[ABSTRACT]
[FINAL]
[CREATE {PUBLIC|PROTECTED|PRIVATE}]
[SHAREDMEMORYENABLED]
[FORTESTING]
[[GLOBAL] FRIENDS class1 class2 ...
intf1 intf2 ...].
PUBLIC
该选项是由Class Builder在创建时生成,只能在class pool上使用,不能用在程序中的局部类中
继承INHERITING FROM
在定义类时(不是现实时),使用INHERTING FROM选项指定从哪个类继承:
CLASS <subclass> DEFINITION INHERITING FROM<superclass>.
父类只能是那些non-final class,并且可见
每个类只能有一个父类,但可以有多个子类,像Java一样,属于单继承
如果未指定此选项,则默认会继承内置的、空的、抽象的object类。且每个继承树的根都是会是object
只有父类的public、protected的组件会继承过来,且继承过来后不会改变其原父类中的可见性。在子类中可以重写父类中继续过来的方法(属性字段没有重写这一说法的)
在重写REDEFINITION父类方法时,不可修改父类中定义的可见性,这与Java不同(java重写时是可以将可见性范围扩大)
继承树上的所有public 、protected组件都会被继承过来,且在同一个same namespace中,所以自定义的属性字段组件(指public、protected可见性属性)不能与继承过来的同名(Java中子类可以重新定义父类中的可见的同名属性,这样会覆盖父类中同名的属性字段,但ABAP根本就不可以这样),但方法是可以重写的;另外,由于父类Private的属性不能继承过来,所在可以在子类中定义同名的属性(此时public、protected、private可见性都可以)
子类和基类中的公有成员以及被保护成员具有共同的命名空间,因此不能重名,而私有成员则在不同类之间可以出现重名的情况
由于public、protected修饰的组件都是可以被子类继承的,所以若在子类定义同名的方法,则一定要定义成REDEFINITION,否则编译无法通过
父类静态的public、protected组件成员也是可以被子类继承的
基类是子类的泛化(Generalization),子类是基类的特殊化(Specialization)
从父类继承过来的方法(在子类中未重写),可以访问父类中的Private成员(与Java是一样的)
ABAP中的继承与Java一样,属于单继承,但可同时实现多个接口
抽象类ABSTRACT
定义为抽象类,此类不能被实例化,只能用来被继承
CLASS cl DEFINITION ABSTRACT.
...
ENDCLASS.
抽象类中可以包含抽象方法,一个抽象方法不能在抽象类本身中被实现,而必须在其子类中被实现,含有抽象方法的类必须被定义为抽象类。在派生类的声明时,使用REDEFINITION关键字对该抽象方法进行重新定义。
终结类FINAL
CLASS class DEFINITION FINAL.
...
ENDCLASS.
定义final类,final类是不能用来被继承的。
final类中的所有方法隐式都是final方法,且final类中的方法不能再明确使用final来修饰
如果某个类定义成了abstract 、final,则只能使用此类中的static的组件(你可以定义非static的实例组件,但你不能够使用它们)
与Java不同的是,可以将某个类同时定义成finalab stract,如果定义成final abstract,则该类定义时最好只定义静态的成员(虽然可以定义非静态成员编译时不会报错,但意义不大,因为final决定了该类不能被继承,而abstract又决定了该类不能被实例化);但相同的是,一个final方法不可以同时是abstract方法。
定时成FINAL类型的类,不能再定义FINAL方法,因为FINAL类型的中所有方法默认就是FINAL了。
CREATE {PUBLIC|PROTECTED|PRIVATE}
该类可以在哪里实例化(即类的可见性),也就是说,CREATE OBJECT语句可以在哪里使用(有点像Java中的类可见修饰符:public、包访问)。
l CREATE PUBLIC:在任何地方类都是可见的(即可被创建)
l CREATE PROTECTED:只能在自己所在类的方法或者是子类中的方法中可见(即可被创建)
l CREATE PRIVATE:只能在自己所在类的方法中可见(即可被创建)
[GLOBAL] FRIENDS
INTERFACE i1.
...
ENDINTERFACE.
CLASS c1 DEFINITIONCREATEPRIVATE FRIENDS i1.
PRIVATESECTION.
DATA a1 TYPEc LENGTH 10VALUE'Class 1'.
ENDCLASS.
CLASS c2 DEFINITION.
PUBLICSECTION.
INTERFACES i1.
METHODS m2.
ENDCLASS.
CLASS c2 IMPLEMENTATION.
METHOD m2.
DATA oref TYPEREFTO c1.
CREATE OBJECT oref.
WRITE oref->a1.
ENDMETHOD.
ENDCLASS.
components
可使用的组件有:
2 TYPE-POOLS, TYPES, DATA, CLASS-DATA, CONSTANTS for data types and data objects
2 METHODS, CLASS-METHODS, EVENTS, CLASS-EVENTS for methods and events
2 INTERFACES(如果在类中,表示需要实现哪个接口;如果是在接口中,表示继承哪个接口) for implementing interfaces and ALIASESfor alias names for interface components给接口组件取别名
属性(Attributes)、方法(Methods)、事件(Events)、内部类型(Types)、常量(Constants)、别名(ALIAS Names)同属同一命名空间,所以这些名称不能相同(与Java不一样)
ABAP类中可以定义三种不同的类型的成员:属性、方法、事件。各种成员的可见性(public protected privte)及生存周期(静态、非静态)只能在类定义时指定,而不是在类实现时指定。
ABAP里的静态与非静态成员是在DATA、METHODS、EVENTS前加上前缀CLASS-来体现的:CLASS-DATA、CLASS-METHODS、CLASS-EVENTS,而不是使用STATIC来修饰的。
类的属性除了DATA(非静态的实例变量)以外,可以为下面语句定义的属性(这些都是静态的?):TYPE-POOLS、TYPES、CLASS-DATA、CONSTANTS
在类或接口中使用CONSTANTS定义的属性是静态的属性,类似于在DATA前加上 CLASS- 前缀而变成静态属性一样,且在定义时就需要赋值。
在类中使用TYPES定义的数据类型,也相当于静态属性。
在类或接口的中使用DATA定义公共属性时,可以加上READ-ONLY选项,表示该属性只能被外部读取,而不能被修改,但可以被内部或子类进行修改。
静态属性在整个继承树中只存在一份,父类的可见静态属性只会加载一次
在类的定义过程中,事件也可以被声明为一种成员组件,声明之后,所有类内部访问均可以触发该事件,还可以在类或者其他类中定义特定的捕捉方法对事件进行处理。
接口组件别名
所有接口组件可以使用 ALIASES 指定其别名(Alias Name),这样在访问接口时,可以简化Interface~前缀。ALIASES只能在接口内或者类的定义部分使用,不能在类的实现部分使用
INTERFACE account.
METHODS calc IMPORTING p1 TYPE i.
ENDINTERFACE.
CLASS cls1 DEFINITION.
PUBLIC SECTION.
INTERFACES account.
ALIASES calc FOR account~calc.
ENDCLASS.
CLASS cls1 IMPLEMENTATION.
"METHOD account~calc.
METHOD calc.
WRITE: / 'Deposit money is:',p1.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA:cref1 TYPE REF TO cls1.
CREATE OBJECT:cref1.
"CALL METHOD:cref1->account~calc EXPORTING p1 = 200.
CALL METHOD:cref1->calc EXPORTING p1 = 200.
"可以去掉 account~ 前缀,使用别名直接进行访问
CALL METHOD:cref1->calc EXPORTING p1 = 200.
实现IMPLEMENTATION
CLASS class IMPLEMENTATION.
...
METHOD ...
...
ENDMETHOD.
...
ENDCLASS.
注:不是所有类都需要有实现部分的。只有在类定义时,定义了方法时,才需要实现,如果没有方法,或者类本身定义成了抽象类,则不需要进行实现了,如下面定义的类就不需要写实现部分了:
CLASS a1 DEFINITION . "由于类中未定义任何方法,所以a1不需要有实现类了
ENDCLASS.
CLASS a1 DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS:m1 ABSTRACT. "由于是抽象方法,所以a1不需要有实现类了
ENDCLASS.
============================
CLASS a1 DEFINITION.
PUBLIC SECTION.
METHODS:m1 .
ENDCLASS.
CLASS a1 IMPLEMENTATION.
METHOD:m1.
ENDMETHOD.
ENDCLASS.
"a2不需要实现,因为继承过来的方法已经被父类自己实现了
CLASS a2 DEFINITION INHERITING FROM a1.
ENDCLASS.
方法的声明
实例方法能访问对象实例中的所有属性和事件以及类的静态成员,而静态方法则只能访问类的静态属性和静态事件
一般方法
METHODS meth [ABSTRACT|FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
[EXPORTING parameters]
[CHANGING parameters]
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
静态方法:
CLASS-METHODS meth [ABSTRACT|FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
[EXPORTING parameters]
[CHANGING parameters]
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
parameters
... { VALUE(p1) | REFERENCE(p1) | p1 }
{ TYPEgeneric_type }
|{TYPE{[LINEOF] complete_type}|{REFTO {data|object|complete_type|class|intf}}}
|{LIKE{[LINEOF] dobj}|{REFTO dobj} }
[OPTIONAL|{DEFAULTdef1}]
{ VALUE(p2) | REFERENCE(p2) | p2 }...]
...
data、object:表示是通用数据类型data、object
complete_type:为完全限定类型
OPTIONAL与DEFAULT两个选项不能同时使用,且对于EXPORTING类型的输入参数不能使用
如果参数名p1前没有使用VALUE、REFERENCE,则默认为还是REFERENCE,即引用传递
不能在方法中修改按引用传递的输入参数,这与Function是一样的
PREFERRED PARAMETER首选参数
设置多个IMPORTING类型参数中的某一个参数为首选参数。
首选参数的意义在于:当所有IMPORTING类型都为可选optional时,我们可以通过PREFERRED PARAMETER选项来指定某一个可选输入参数为首选参数,则在以下简单方式调用时:
实参a的值就会传递给设置的首参,而其他不是首参数的可选输入参数则留空或使用DEFAULT设置的默认值
注:此选项只能用于IMPORTING类型的参数;如果有必选的IMPORTING输入参数,则没有意义了
RAISINGexc1 exc2
exc1 exc2为class-based exceptions的异常,如果方法运行时出现了exc1 exc2异常,则会抛出
exc1 exc2为CX_STATIC_CHECK 或者是CX_DYNAMIC_CHECK的子类,如果需要抛出多个异常,且有父子关系,则子的应该排在前面
CX_STATIC_CHECK 或者是CX_DYNAMIC_CHECK类型的异常需要显示的抛出,而CX_SY_NO_HANDLER类型的异常不需要
CLASS math DEFINITION.
PUBLIC SECTION.
METHODS divide_1_by
IMPORTING operand TYPE i
EXPORTING result TYPE f
RAISING cx_sy_arithmetic_error.
ENDCLASS.
CLASS math IMPLEMENTATION.
METHOD divide_1_by.
result = 1 / operand.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA oref TYPEREFTO math.
DATA exc TYPEREFTO cx_sy_arithmetic_error.
DATA res TYPE f.
DATAtextTYPE string.
CREATEOBJECT oref.
TRY.
oref->divide_1_by( EXPORTING operand = 4
IMPORTING result = res ).
text = res.
CATCH cx_sy_arithmetic_error INTO exc.
text = exc->get_text( ).
ENDTRY.
MESSAGEtextTYPE'I'.
EXCEPTIONS exc1 exc2
exc1 exc2为non-class-based exceptions类型的异常,可以由方法中的RAISE 或者 MESSAGE RAISING语句来触发
CLASS math DEFINITION.
PUBLICSECTION.
METHODS divide_1_by
IMPORTING operand TYPEI
EXPORTING result TYPE f
EXCEPTIONS arith_error.
ENDCLASS.
CLASS math IMPLEMENTATION.
METHOD divide_1_by.
TRY.
result = 1 / operand.
CATCH cx_sy_arithmetic_error.
RAISE arith_error.
ENDTRY.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA res TYPE f.
DATA oref TYPEREFTO math.
CREATE OBJECT oref.
oref->divide_1_by( EXPORTING operand = 4
IMPORTING result = res
EXCEPTIONS arith_error = 4 ).
IFsy-subrc = 0.
WRITE res.
ELSE.
WRITE'Arithmetic error!'.
ENDIF.
抽象方法ABSTRACT
ABSTRACT关键字可修饰方法也修饰类,且定义抽象方法时,只能用在抽象类(Abstract Class)里面(接口与非抽象类定义里不能直接用来修改方法)
静态的方法不能定义成Abstract的
抽象方法一定要在其子类声明部分使用REDEFINITION关键字进行重写实现,不能在自己所在实现类中写方法体
接口中的方法默认就是ABSTRACT
Abstract Final修饰的类中可以定义抽象方法,但该类没有什么用(不能被继承,所以不能被实现),并且Abstract Final修饰的类中可以定义实例方法,不像Java那样严格
终结方法FINAL
METHODS meth FINAL...
FINAL关键字可修饰方法也修饰类
FINAL修饰的方法只能在类中进行声明,不能在接口中进行声明。
最终方法是不可以重新定义(重写)的方法,不一定出现在最终类中,但最终类中的所有方法都是最终方法,因此不能再重复使用FINAL关键字了
与Java不同的是,可以将某个类同时定义成finalabstract,如果定义成final abstract,则该类定义时最好只定义静态的成员(虽然可以定义非静态成员编译时不会报错,但意义不大,因为final决定了该类不能被继承,而abstract又决定了该类不能被实例化);但与Java相同的是,一个final方法不可以同时是abstract方法。
函数方法(Return唯一返回值)
非静态
METHODS meth [ABSTRACT|FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
RETURNING VALUE(r) typing
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
静态
CLASS-METHODSmeth [ABSTRACT|FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
RETURNING VALUE(r) typing
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
RETURNING :用来替换EXPORTING 、CHANGING,不能同时使用。定义了一个形式参数 r 来接收返回值,并且只能是值传递
此处的r只能是完全限制类型,不能是通用类型typing:
{ TYPE {[LINEOF] complete_type}
|{REFTO {data|object|complete_type|class|intf}} }
|{ LIKE {[LINEOF] dobj}| {REFTO dobj} } ...
具有唯一返回值的函数方法可以直接用在以下语句中:
o 逻辑表达式:IF、ELSEIF、WHILE、CHECK、WAIT
o CASE
o LOOP
o 算术表达式
o 赋值语句
此类方法可以采用简单调用方式:
meth( )
meth( a )
meth( p1 = a1 P2 = a2 ... )
CLASS math DEFINITION.
PUBLICSECTION.
METHODS factorial
IMPORTING n TYPEi
RETURNINGvalue(fact) TYPEi.
ENDCLASS.
CLASS math IMPLEMENTATION.
METHOD factorial.
fact = 1.
IF n = 0.
RETURN.
ELSE.
DO n TIMES.
fact = fact * sy-index.
ENDDO.
ENDIF.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA oref TYPEREFTO math.
DATA result TYPEi.
CREATEOBJECT oref.
result = oref->factorial( 4 ).
WRITE: result.
构造函数constructor
非静态
METHODS constructor [FINAL]
[IMPORTING parameters [PREFERRED PARAMETER p]]
[{RAISING|EXCEPTIONS} exc1 exc2 ...].
构造函数的名是固定的,即为constructor
构造方法默认就是FINAL的,所以没有必要加
构造器必须放在Public段中进行声明
构造方法中不能包含任何形式的输出参数和返回值
如果在对象创建并调用构造方法的过程中出现异常,则该对象将被删除
构造函数没有输出参数,只有输入参数,且无返回值
每个类都会有一个默认的无参构造器,但如果重新定义了构造器,则会覆盖默认构造器
在类的创建过程中,有两个特殊的方法,一个是实例的构造函数constructor,该方法在实例创建时隐式的调用;另一个方法是类构造函数class_constructor,该方法在第一次访问类时被调用,且只调用一次。
如果某个类继承了别的类(Object除外),这个某个类又重新定义了构造器,则一定要在构造器中明确使用super->constructor方法来调用一下父类构造器(否则编辑不通过),只有这样才能保证整个对象被完整地构造出来,因为子类只能正确地构造出属于自己的那部分属性,对于从基类继承来的那部分属性要调用基类中的构造方法来完成。在调用父构造器前,只能访问父类中静态的组件,只有在调用了父类构造器后,才能去访问父中的实例组件,这与Java不太一样(Java在明确调用父类构造器时,要放在第一句)
如果在一个子类里重写了默认构造函数constructor,则在实现时一定要明确的使用CALL METHOD super->constructor.调用一下父类的构造函数,但不一定是在第一句进行调用,这与Java有点不一样:Java可以重写默认构造函数,且重写时不一定要明确调用父类构造函数,因为它会默认调用默认构造函数,且调用时只能是在第一句。在ABAP中子类重写构造函数不需要明确调用父类构造函数的唯一列外是该类继承的是OBJECT类。
如果某个类重写了父类某个方法,则在父类构造器中如果调用该方法,调用到的方法还是父类的方法,而不是被重写后的子类方法,这与Java又不一样(Java中不管重写的方法是抽象的还是非抽象的,都是可以回调到子类重写过的方法),所以不要在父类方法中回调子类被重写过非Abstract方法,因为根本无法回调到,调到还是父类自己的方法:
CLASS a1 DEFINITION.
PUBLIC SECTION.
METHODS :constructor ,
m1,m2.
ENDCLASS.
CLASS a2 DEFINITION INHERITING FROM a1.
PUBLIC SECTION.
METHODS: constructor ,
m1 REDEFINITION,m2 REDEFINITION.
ENDCLASS.
CLASS a1 IMPLEMENTATION.
METHOD: constructor.
"调用的是父类中的方法,而不是子类被重写的方法,这与Java不一样
me->m1( ).
ENDMETHOD.
METHOD: m1.
WRITE: / 'a1~m1'.
"即使不是在父类构造器中调用被重写方法也是这样的,与Java不一样
me->m2( ).
ENDMETHOD.
METHOD: m2.
WRITE: / 'a1~m2'.
ENDMETHOD.
ENDCLASS.
CLASS a2 IMPLEMENTATION.
METHOD: constructor.
super->constructor( ).
SKIP.
me->m1( ).
ENDMETHOD.
METHOD: m1.
WRITE: / 'a2~m1'.
me->m2( ).
ENDMETHOD.
METHOD: m2.
WRITE: / 'a2~m2'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA: o TYPE REF TO a1.
CREATE OBJECT o TYPE a2.
SKIP.
o->m1( ).
另外,不能在构造器中调用抽象方法,所以要想在父类的方法中回调到时子类重定义方法,该被重写的方法一定要是父类中定义的抽象方法,这与设计模式中的模板方法设计模式是相当的,以下是使用ABAP面向对象来实现模板方法设计模式:
CLASS a1 DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: m1,m2 ABSTRACT.
ENDCLASS.
CLASS a2 DEFINITION INHERITING FROM a1.
PUBLIC SECTION.
METHODS: m2 REDEFINITION.
ENDCLASS.
CLASS a1 IMPLEMENTATION.
METHOD: m1.
"模板模式:在父类中回调子类实现的父类中所定义的抽象方法
me->m2( ).
ENDMETHOD.
ENDCLASS.
CLASS a2 IMPLEMENTATION.
METHOD: m2."实现父类中定义的抽象方法
WRITE: / 'a2~m2'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA: o TYPE REF TO a1.
CREATE OBJECT o TYPE a2.
SKIP.
o->m1( )."调用继承过来的父类方法
静态
CLASS-METHODSclass_constructor.
静态的构造函数是每个类都已具有的方法,但我们可以通过重定义来完成某些初始化工作
静态的构造器只在程序中被调用一次,即第一次作用该类调用时,且不能被程序显示地调用
该方法也必须在公共部分采用CLASS-METHODS声明,其名称必须是class_constructor,且没有参数(因为不能被显示的调用)
并且在此方法里只能访问类的静态属性
CLASS a1 DEFINITION.
PUBLIC SECTION.
class-METHODS: class_constructor .
ENDCLASS.
CLASS a1 IMPLEMENTATION.
METHOD: class_constructor.
WRITE:/ '类已加载'.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA: o TYPE REF TO a1.
"在每一次使用类时,静态的构造函数会自动调用
CREATE OBJECT o.
方法重写(Overriding)与重载(Overloading)
在类的声明中,通过REDEFINITION选项告诉其实现类要重写父类的meth方法(实例请参考这里):
METHODS meth [FINAL] REDEFINITION.
重写一个方法首先需要在子类的类声明中用“REDEFINITION”进行重定义,如果被重写的方法具有参数,则在子类的重定义中不必再重新声明一遍参数列表了,而只需要写函数的名称即可,这是因为ABAP面向对象中,不支持方法之间的重载,即在同一个类中不允许存在两个同名的方法而不管其参数列表是否一致(注:虽然父与子类构造器允许重新定义构造函数constructor,虽然名称相同,但还是可以都重定定义,不会出现同名问题,因为构造函数属于各自的命名究竟,这不属于重写或重载父类中的构造函数,构造函数是一种特殊的方法);在子类重写的方法中,可以使用“super->”来访问被重写的父类中的成员,super->XXX( ) 只能用于子类中被重写的方法XX中,其他方法中不能直接使用(这与Java不太一样),其作用只是防止循环调用?
重定义(重写)方法必须在子类中被重新实现,且重定义的方法参数不能被更改
静态方法不可以被重写,这与Java一样。一个类的非私有的静态属性将被所有该类的子类所共享
创建对象CREATE OBJECT
在调用CREATE OBJECT语句时,会自动调用类的构造器
在创建过程如果出现异常,则oref为初始值,并且sy-subrc为非零(EXCEPTIONS选项设置的异常值)
根据引用变量(oref)类型来创建对象
CREATE OBJECT oref [AREA HANDLE handle]
[{ [EXPORTING p1 = a1 p2 = a2 ...]
[EXCEPTIONS exc1 = n1 exc2 = n2 ...] }
| { [PARAMETER-TABLE ptab][EXCEPTION-TABLE etab] }].
此种方式创建对象时,对象的类型由oref定义时的类型来决定,如下面的创建的对象类型为c1:
CLASS c1 DEFINITION.
ENDCLASS.
DATA oref TYPE REF TO c1.
CREATE OBJECT oref.
注:oref的类型c1必须是一个可以实例化的具体类,不能是接口、抽象类、以及Object
创建时明确指定对象类型(TYPE class)
CREATE OBJECT oref [AREA HANDLE handle]
TYPE {class|(name)}
[{ [EXPORTING p1 = a1 p2 = a2 ...]
[EXCEPTIONS exc1 = n1 exc2 = n2 ...] }
| { [PARAMETER-TABLE ptab][EXCEPTION-TABLE etab] }].
此种方式创建的对象类型则这里的class或动态(name)来决定,而非oref定义的类型,如下面创建的对象最终其真正类型为c1,而非object(其实这里就是可以使用父类的引用指向子类对象而已):
CLASS c1 DEFINITION.
ENDCLASS.
DATA oref TYPEREFTO object.
CREATEOBJECT oref TYPE c1.
class或动态(name)必须是一个可实例化的具体类(也不能是不能是接口、抽象类、以及Object),且为oref定义的类型或者是其子类。而oref的类型则可以是类、或者是接口,甚至是Object
(name)为动态指定类名,且为大写,可以是决定类型名称(The RTTS can determine the absolute type name of any object.)
C2为C1的子类:
DATA o1 TYPEREFTO c1.
DATA o2 TYPEREFTO c2.
CREATE OBJECT o2.
o1 = o2.
这里的o2是一个引用类型变量,其实就是一个指针,用来存储对象的地址,而CREATE语句就是创建了一个对象并让o2指向它,并且最后将o2赋值给o1,这样o1也会指向o2所指向的同一对象
成员访问
oref->comp
其中,oref代表对象引用变量,comp代表要访问的类成员,“->”为对象组件选择运算符,可以用于访问类中定义的实例组件和静态组件。对于类中的静态组件而言,还有另外一种访问方式,可以不通过实例和引用就可以进行访问,而是通过类名称本身使用类成员选择运算符“=>”直接进行操作:
class=>comp
由于对象引用变量本身也可以被定义为类属性,因而可以出现下述的链式访问结构:
oref1->oref2->comp
或者
class=>oref->comp
在类的实例方法中,与Java一样有一个当前指针this,只不过名字为me。
访问内容 |
语法格式 |
一个对象的实例属性或静态属性 |
oref->attr |
类的静态属性 |
class=>attr |
在类内部访问自身实例属性或静态属性 |
me->attr或attr |
对象的实例方法或静态方法 |
CALL METHOD oref->meth |
类的静态方法 |
CALL METHOD class=>meth 注:不能是oref=>meth |
在类内部访问自身实例方法或静态方法 |
CALL METHOD me->meth或 CALL METHOD meth |
me(this)
me 就是相当于Java中的this,指向当前实例对象
用me引用变量可以引用类中的成员变量或方法
当方法的一个参数名或方法内部的局部变量名与类的成员变量的名字重复的时候,要在该方法内部引用类成员变量xxx时,就必须使用me->xxx 来引用
方法调用
静态调用(注:这里的静态不是指调用静态方法)
[CALLMETHOD] meth|me->meth|oref->meth|super->meth|class=>meth[(]
[EXPORTING p1 = a1 p2 = a2 ...]
{{[IMPORTING p1 = a1 p2 = a2 ...]
[CHANGING p1 = a1 p2 = a2 ...]}
|[RECEIVING r = a ] }
[EXCEPTIONS [exc1 = n1 exc2 = n2 ...]
[OTHERS = n_others] ] [)].
如果省略CALL METHOD,则一定要加上括号形式;如果通过CALL METHOD来调用,则括号可加可不加
RECEIVING:用来接收METHODS 、CLASS-METHODS 中RETURNING选项返回的值
等号左边的都是形式参数,右边为实参,EXPORTING指定输入参数,IMPORTING指定输出参数,这与函数的调用相似(调用用时与定义是相反的)
如果EXPORTING 、IMPORTING 、CHANGING、RECEIVING、EXCEPTIONS、OTHERS同时出现时,应该按此顺序来编写
注:此种方式调用时,如果原方法声明时带了返回值RETURNING,只能使用RECEIVING来接受,而不能使用等号来接收返回值:
num2 =o1->m1( EXPORTING p1 = num1 ).
简单调用方式
[CALLMETHOD] { meth|me->meth|oref->meth|super->meth|class=>meth( )
| meth|me->meth|oref->meth|super->meth|class=>meth( a )
| meth|me->meth|oref->meth|super->meth|class=>meth( p1 = a1 p2 = a2 ... ) }.
此方式下输入的参数都只能是IMPORTING类型的参数,如果要传CHANGING、EXPORTING、RAISING、EXCEPTIONS类型的参数时,只能使用上面静态调用方式。
2 meth( )
此种方式仅适用于没有输入参数(IMPORTING)、输入\输出参数(CHANGING)、或者有但都是可选的。
通过此种简单方式调用时,会忽略掉EXPORTING输出参数,即不能接收EXPORTING输出值
2 meth( a )
此种方式仅适用于只有一个必选输入参数(IMPORTING)(如果还有其他输入参数,则其他都为可选,或者不是可选时但有默认值也可),或者是有多个可选输入参数(IMPORTING)(此时没有必选输入参数情况下)的情况下但方法声明时通过使用PREFERRED PARAMETER选项指定了其中某个可选参数为首选参数(首选参数即在使用meth( a )方式传递一个参数进行调用时,通过实参a传递给设置为首选的参数)
此情况下可以带可选的OPTIONAL类型的输入输出(input/output)CHANGING类型的参数,以及输出参数EXCEPTIONS,但这些都会被忽略
2 meth( p1 = a1 p2 = a2 ... )
此种方式适用于有多个必选的输入参数(IMPORTING)方法的调用,如果输入参数(IMPORTING)为可选,则可以不必传
动态调用
CALL METHOD (meth_name)
| cref->(meth_name)
| iref->(meth_name)
| (class_name)=>(meth_name)
| class=>(meth_name)
| (class_name)=>meth
{
[EXPORTING p1 = a1 p2 = a2 ...]
{{[IMPORTING p1 = a1 p2 = a2 ...][CHANGING p1 = a1 p2 = a2 ...]}
|[RECEIVING r = a ] }
[EXCEPTIONS [exc1 = n1 exc2 = n2 ...]
[OTHERS = n_others] ]
}
|
{[PARAMETER-TABLE ptab][EXCEPTION-TABLE etab]}.
PARAMETER-TABLE ptab
ptab为ABAP类型组中定义的哈希表ABAP_PARMBIND_TAB,行结构为ABAP_PARMBIND:
* PARAMETER-TABLE
BEGIN OF abap_parmbind,
name TYPE abap_parmname,
kind TYPE abap_parmkind,
value TYPE REF TO data,
ENDOF abap_parmbind
abap_parmbind_tab TYPE HASHED TABLE OF abap_parmbind WITH UNIQUE KEY name,
name:形式参数的名,且为大写
kind:形式参数的类别,具取值为CL_ABAP_OBJECTDESCR 类的常量EXPORTING, IMPORTING, CHANGING, RECEIVING:
value:参数值,类型为数据引用,即实参的地址(可以是所有类型变量的地址,如普通数据就是,或引用对象等),使用:GETREFERENCEOFpnameINTOptab-value来获取实参地址
EXCEPTION-TABLE etab
non-class-based exceptions,etab为ABAP类型组中定义的哈希表ABAP_EXCPBIND_TAB,行结构为ABAP_EXCPBIND:
* EXCEPTION-TABLE
BEGINOFabap_excpbind,
name TYPE abap_excpname,形式参数名,且大写,还可以是OTHERS
value TYPEi,异常值,出现异常后,此值会存储到sy-subrc系统变量中
ENDOF abap_excpbind,
abap_excpbind_tabTYPEHASHEDTABLEOF abap_excpbind
WITHUNIQUEKEY name.
垃圾回收
ABAP与Java一样也有垃圾回收机制,当没有引用变量再指向某个对象时,将会被自动回收,所以如果要加快垃圾回收,可以使用CLEAR语句初始化指向它的引用变量(相当于Java中给引用设置为null一样),或者使指向它的引用指向其他对象。
多态(转型)
向上转型(Up Casting):多态
向下转型(Down Casting):需要进行强转
上图中 student_ref = person_ref. 语句因该有问题,需强转,如下:
ENDCLASS.
CLASS stud DEFINITION INHERITING FROM person.
ENDCLASS.
START-OF-SELECTION.
DATA p TYPE REF TO person.
DATA s TYPE REF TO stud.
CREATE OBJECT s.
p = s.
"拿开注释运行时抛异常,因为P此时指向的对象不是Student,而是Person
"所以能强转的前提是P指向的是Student
"CREATE OBJECT p.
s ?= p."需这样强转
抽象类(ABSTRACT)和最终类(FINAL)
CLASS class DEFINITION ABSTRACT.
...
ENDCLASS.
抽象类不能使用 CREATE OBJECT 语句创建类对象。
也可以将方法定义为抽象方法:
METHOD meth ABSTRACT ...
一个抽象方法不能在类本身中被实现,而必须在其派生出的非抽象类中实现。因而含有抽象方法的类必须被定义为抽象类。
最终类(Final)是不能被继承的类:
接口Interface
针对接口编程:
接口隔离:
定义接口
INTERFACE intf [PUBLIC].
[components]
ENDINTERFACE.
接口中可以定义以下一些组件(Java接口中定义的属性成员都是 public static 的,而方法都是public abstract,但ABAP接口中定义的属性与方法可以是静态的也可以是非静态的):
INTERFACE i1.
CLASS-DATA a2 TYPE i .
CONSTANTS a3 TYPE i VALUE 1."相当于静态的
CLASS-METHODS m2.
CLASS-EVENTS e2.
TYPES a TYPE c LENGTH 2.
DATA a1 TYPE string .
METHODS m1.
EVENTS e1 EXPORTING value(p1) TYPE string.
ENDINTERFACE.
=================================================
CLASS aa DEFINITION.
PUBLIC SECTION.
INTERFACES i1.
ENDCLASS.
CLASS aa IMPLEMENTATION.
METHOD i1~m1.
WRITE:i1~a2.
WRITE:i1~a1.
WRITE:i1~a3.
ENDMETHOD.
METHOD i1~m2.
WRITE:i1~a2.
"WRITE:i1~a1."静态方法中不能访问非静态属性
WRITE:i1~a3.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA obj TYPE REF TO aa.
CREATE OBJECT obj.
WRITE: aa=>i1~a2.
WRITE: obj->i1~a2.
WRITE: obj->i1~a1.
WRITE: aa=>i1~a3.
WRITE: obj->i1~a3.
接口中所定义的所有东西默认都是公共的,所以不用也不能写PUBLIC SECTION
在接口定义内部可以声明的成员与类中的成员相同(包括属性、方法和事件等,与Java中的接口不太一样就是可以有非静态的成员属性),但无需注明具体的可见性,因为所有接口组件成员均为公有成员。同时,接口定义中也只能包含成员声明部分,而具体的实现将在具体类中进行。
只有在接口中定义的常量(CONSTANTS)才可以使用 VALUE 选项来初始化,而DATA、CLASS-DATA定义的变量是不能初始化。
实现接口
如果要实现某个接口,则在类的公共声明PUBLIC SECTION部分(其他Protected、Private不行),使用以下语句来完成:
INTERFACES intf
{ {[ABSTRACTMETHODS meth1 meth2 ... ]
[FINALMETHODS meth1 meth2 ... ]}
| [ALLMETHODS {ABSTRACT|FINAL}] }
[DATAVALUES attr1 = val1 attr2 = val2 ...].
可以使用ABSTRACT METHODS、FINAL METHODS选项来指定方法meth1 meth2 ...为abstract 或者是 final类型的方法,这与METHODS语句的ABSTRACT、FINAL选项的意义是一样的。当把接口中的某个方法声明在ABSTRACTMETHODS选项后面时,这个类一定要声明成抽象类,并且接口中的方法不能同时出现在ABSTRACTMETHODS与FINALMETHODS后面。
如果接口中的方法都不实现或实现后都不能再被重写时,可以使用ALLMETHODS {ABSTRACT|FINAL}选项快捷操作
可以使用DATAVALUES选项来为接口中定义的属性赋初始值,接口使用CONSTANTS定义的属性为常量,应该在接口声明时就使用VALUE选项赋值,并且在实现时不能再修改其值了
可以在实现接口的同时,继承某个类
接口的实现要通过类声明时进行,在类中实现一个接口的语法如下:
CLASS class DEFINITION.
PUBLIC SECTION.
...
INTERFACES: int1,int2."可实现多个接口
...
ENDCLASS.
在类定义中,接口的实现(INTERFACES)只能出现在公有部分,接口中定义的所有组件都将被添加为该类的公有成员。
接口intf中的组件(属性、方法、事件)icomp在类内部实现后是以名称intf~icomp的形式出现。在类的实现部分,必须实现所有接口方法,除非将类定义为抽象类abstract:
CLASS class IMPLEMENTATION.
METHOD intf1~imeth1.
...
ENDMETHOD.
...
ENDCLASS.
=================================================
INTERFACE i1.
DATA a1 TYPE string.
METHODS m1.
EVENTS e1 EXPORTINGvalue(p1) TYPE string.
ENDINTERFACE.
CLASS c1 DEFINITION.
PUBLICSECTION.
INTERFACES i1.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHODi1~m1.
RAISEEVENT i1~e1EXPORTING p1 = i1~a1.
ENDMETHOD.
ENDCLASS.
接口“继承”
INTERFACES intf.
ABAP中的接口“继承”与Java还是不一样的,这里的“继承”只是简单的Include进来,并且具有各自的名称空间,在实现类中使用时需要加上前缀“接口名~”才能定位到,而且如果是多层接口继承,这些多接口在最终实现时,在实现类里都属性同一级别,即都是使用“接口名~”来定位,而不是使用多级别“接口名0~接口名1~…”来定位。其实ABAP中的接口继承只是一个接口的整合(集成)而已!
INTERFACE i0.
METHODS m0.
ENDINTERFACE.
INTERFACE i1.
INTERFACES i0.
"可以有相同的成员名,因为继承过来后,成员还是具有各自的命名空间,在实现时
"被继承过来的叫 i0~m0,在这里的名为i1~m0,所以是不同的两个方法
METHODS m0.
METHODS m1.
ENDINTERFACE.
INTERFACE i2.
INTERFACES i1.
METHODS m2.
ENDINTERFACE.
INTERFACE i3.
INTERFACES i1.
METHODS m3.
ENDINTERFACE.
CLASS c1 DEFINITION.
PUBLICSECTION.
"这一句可以去掉,因为下面语句中的i2、i3已经继承(包括)了i0、i1接口了
INTERFACES: i0,i1.
INTERFACES: i2, i3.
METHODS:m4.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD i0~m0."i0~m0与i1~m0有各自的命名空间,所以都要实现
...
ENDMETHOD.
METHOD i1~m0."i0~m0与i1~m0有各自的命名空间,所以都要实现
...
ENDMETHOD.
METHOD i1~m1.
...
ENDMETHOD.
METHOD i2~m2.
...
ENDMETHOD.
METHOD i3~m3.
...
ENDMETHOD.
METHOD m4.
...
ENDMETHOD.
ENDCLASS.
DATA iref1 TYPEREFTO c1.
START-OF-SELECTION.
CREATE OBJECT iref1 .
"通过实现类访问接口中的方法时,需要加上接口名称前缘,否则访问不到
iref1->i0~m0( )."注:不是iref1->i2~i1~i0~m0( )或iref1->i3~i1~i0~m0( )
iref1->i1~m0( ).
iref1->i1~m1( ).
iref1->i2~m2( ).
iref1->i3~m3( ).
"访问自己的方法
iref1->m4( ).
访问接口成员
不管是静态的还是非静态的,不管是属性还是方法,只要是通过实现类类名或实现类引用变量来访问接口中定义的成员,则要在组件前加上接口名~前缀才能访问。
只有接口中的常量可以直接使用接口名直接来访问接口名=>CONSTANTS(当然也可以是实现类名=>接口名~ CONSTANTS或实现类类型引用变量->接口名~CONSTANTS或者接口类型引用变量->CONSTANTS),接口中的静态属性与静态方法都是不能直接使用“接口名=>xxx”的形式直接来访问(当然非静态的成员就更不用说了),这是因为除了CONSTANTS属性外,静态属性可以在实现类中重新赋值,静态方法需要在实现类中进行现实(虽然不叫重写),所以就不允许直接使用接口名“接口名=>xxx”的形式来访问使用CLASS-定义的组件了,但可以通过实现类名=>接口名~静态属性或方法或实现类类型引用变量->接口名~静态属性或方法或者接口类型引用变量->静态属性或方法来访问
接口中的非静态属性与方法可以通过实现类类型引用变量->接口名~非静态属性或方法或者接口类型引用变量->非静态属性或方法的形式来访问
注:这里描述的实现类类型引用是指引用变量在定义时就使用的是实现类类型,而不是接口类型,即DATA iref1 TYPEREFTO xxx.语句中的xxx为实现类类名,而不是接口名;接口类型引用变量是指定引用变量在定义时为接口类型,而在创建对象时才指定其具体实现类,即:
DATA iref1 TYPEREFTO i0.
CREATEOBJECT iref1 TYPE c1.
i0为接口,c1为i0的实现类,所以这种使用接口类型引用变量来访问非静态的方法属于一种多态的表现
强转型(?=)
假设存在类引用变量cref 和接口引用变量 iref、iref1和iref2,则:
iref1 = iref2.
这两个接口必须参照同一个接口声明,或者iref2是iref1的一个子接口。
iref = cref.
则cref参照的类必须是接口iref的实现。
cref = iref.
则cref参照的类必须是根类OBJECT。
如果两个引用变量不兼容(比如向下转型),则要使用强制赋值运算符“?=”进行:
cref ?= iref.
DATA: descr_ref1 TYPE REF TO cl_abap_elemdescr.
descr_ref1 ?= cl_abap_elemdescr=>describe_by_data( obj ).
由于cl_abap_elemdescr=>describe_by_data( obj )返回的类型为CL_ABAP_TYPEDESCR,而CL_ABAP_TYPEDESCR又为cl_abap_elemdescr的父类,所以需要强转
在进行强制赋值时,系统不会出现任何静态语法检查,但系统将在运行时检查目标对象引用变量是否可以指向源变量引用的对象。如果不允许,则会抛MOVE_CAST_ERROR系统异常,可以使用以下捕获异常的代码来判断强转是否成功,这样即使强转不成功,也不会抛异常:
DEFINE mmpur_dynamic_cast.
catch system-exceptions move_cast_error = 1.
&1 ?= &2.
endcatch.
if sy-subrc = 1.
clear: &1.
endif.
END-OF-DEFINITION.
DEFINE mmpur_dynamic_cast1.
try.
&1 ?= &2.
catch cx_sy_move_cast_error.
clear &1.
endtry.
END-OF-DEFINITION.
mmpur_dynamic_cast lw_header im_model."强制向下转型
CHECK NOT lw_header IS INITIAL."如果强转不出错,则继续执行后续代码
事件Events
通过事件处理机制,可让一些组件作为事件源,发出可被描述环境或其它组件接收的事件。这样,不同的组件就可在构造工具内组合在一起,组件之间通过事件的传递进行通信,构成一个应用。从概念上讲,事件是一种在"源对象"和"监听者对象"之间,某种状态发生变化的传递机制。源对象产生一个事件对象,事件对象中包括了事件源对象与相应的参数,然后传递给事件处理器(事件监听者)进行相应的处理。
JavaBean的事件处理机制
事件状态对象( Event State Object ):
与事件发生有关的状态信息一般都封装在一个事件状态对象中,这种对象是java.util.EventObject的子类。
publicclassEventObjectimplements java.io.Serializable {
protectedtransient Object source;//为事件源
public EventObject(Object source) {
if (source == null)
thrownew IllegalArgumentException("null source");
this.source = source;
}
public Object getSource() {
returnsource;
}
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
//创建一个鼠标移动事件MouseMovedExampleEvent
publicclassMouseMovedExampleEventextends java.util.EventObject{
protectedintx, y;
//source为事件源
MouseMovedExampleEvent(java.awt.Component source, java.awt.Point location) {
super(source);
x = location.x;
y = location.y;
}
//获取鼠标位置
public java.awt.Point getLocation() {
returnnew java.awt.Point(x, y);
}
}
事件侦听者接口( EventListener Interface )
publicinterfacejava.util.EventListener {
}
interface MouseMovedExampleListener extends java.util.EventListener {
//mme为事件对象,该方法为事件处理方法
void mouseMoved(MouseMovedExampleEvent mme);
}
}
事件侦听者的注册与注销
publicabstractclass Model {
// 储存事件监听者
private Vector listeners = new Vector();
// 注册
publicsynchronizedvoid addModelChangedListener(ModelChangedListener mcl) {
listeners.addElement(mcl);
}
// 注销
publicsynchronizedvoid removeModelChangedListener(ModelChangedListener mcl) {
listeners.removeElement(mcl);
}
protectedvoid notifyModelChanged() {
Vector l;
EventObject e = new EventObject(this);
/*
* 首先要把监听者拷贝到l数组中,冻结EventListeners的状态以传递事件。
* 这样来确保的在循环发送事件时,即使其它线程对监听者集合进行了增删,也不会
* 影响原来需要触发的监听者集合。这里的同步块只是在拷贝时同步,在调用时没同
*步,因为调用监听者方法或能是个长方法,所以先拷贝
*/
synchronized (this) {
l = (Vector) listeners.clone();
}
for (int i = 0; i < l.size(); i++) {
/*
* 依次通知注册在监听者队列中的每个监听者发生了modelChanged事件,
* 并把事件状态对象e作为参数传递给监听者队列中的每个监听者
*/
((ModelChangedListener) l.elementAt(i)).modelChanged(e);
}
}
}
Java观察者(Observer)模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
下面是被观察者,从API复制过来,但将Vector改为了ArrayList。在实现的应用中我们直接使用类库中的Observable即可。
Public class Observable {//抽象主题,被观察者
Private boolean changed = false;//状态是否改变
Private ArrayList obs = new ArrayList();//登记观察
//观察者注册
Public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.add(o);
}
}
//注册某个观察者
Public synchronized void deleteObserver(Observer o) {
obs.remove(o);
}
Public void notifyObservers() {
notifyObservers(null);
}
/*
* 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并
* 调用 clearChanged 方法来指示此对象不再改变。
*/
Public void notifyObservers(Object arg) {
Object[] arrLocal;
/*
* 在同步块中拷贝一个,这样的拷贝会很快,但通知所有
* 可能比较慢,所以为了提高性能需要拷贝一个,在通知
* 时不会阻塞其他操作
*/
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length - 1; i >= 0; i--)
((Observer) arrLocal[i]).update(this, arg);
}
Public synchronized void deleteObservers() {
obs.clear();
}
Protected synchronized void setChanged() {
changed = true;
}
Protected synchronized void clearChanged() {
changed = false;
}
Public synchronized boolean hasChanged() {
return changed;
}
Public synchronized int countObservers() {
returnobs.size();
}
}
在实际的应用中直接继承类库中的Observable即可。
Public class ConcreteObservable extends Observable {//具体主题
private String state;//具体主题状态
public String getState() {
return state;
}
publicvoid setState(String state) {
this.state = state;//改变状态
this.setChanged();//通知前需要设置成改变状态才起作用
this.notifyObservers(state);//通知所有观察者
}
}
下面是观察者,从API复制过来,在实现的应用中我们直接使用类库中的Observer接口即可。
Public interface Observer {//抽象观察者
/**
* 主题状态改变时调用
* @param o observable 对象
* @param state 传递给 notifyObservers 方法的参数
*/
void update(Observable o, Object state);
}
在实际应用中我们直接实现类库中的Observer接口即可。
Public class ConcreteObserver implements Observer {//具体观察者
/**
* @param o observable 对象,便于回调用被观察者方法
* @param state 传递给 notifyObservers 方法的参数
*/
publicvoid update(Observable o, Object state) {
System.out.println("I am notified. countObservers="
+ o.countObservers() + " newState=" + state);
}
}
Public class Client {//场景
Public static void main(String[] args) {
ConcreteObservable obs = new ConcreteObservable();
obs.addObserver( new ConcreteObserver());
obs.addObserver( new ConcreteObserver());
obs.setState("stat1");
obs.setState("stat2");
}
}
事件定义
事件是没有继承关系的类之间可以相互调用彼止方法的特殊方法
EVENTS|CLASS-EVENTS evt [EXPORTING VALUE(p1)
{ TYPE generic_type }
|{TYPE {[LINEOF] complete_type}|
{REF TO {data|object|complete_type|class|intf}} }
| {LIKE{[LINE OF] dobj}|{REF TO dobj} }
[OPTIONAL|{DEFAULT def1}]
VALUE(p2) ...].
data、object:表示是通用数据类型data、object
complete_type:为完全限定类型
OPTIONAL与DEFAULT两个选项不能同时使用
EVENTS定义的为非静态事件,只能在本类实例方法中触发,而CLASS-EVENTS定义的为静态事件,可以在本类中的任何方法中触发
事件evt的定义是在在类或接口定义部分,使用EVENTS、CLASS-EVENTS语句进行事件的声明的。可以在本类或者是实现了该接口中子类中的实例或静态方法中,使用RAISE EVENT语句来触发定义的实例事件。注:非静态事件只能被该类中的非静态(实例)方法来触发,但静态事件既可由静态方法来触发,也可由非静态方法来触发;或者反过来说:实例方法既可触发静态事件,也可触发非静态事件,但静态方法就只能触发静态事件
EXPORTING:定义了事件的输出参数,并且事件定义时只能有输出参数(事件声明时只能有输出参数,这对应事件处理器——监听者方法的输入参数),并且只能传值的方式。当在METHODS 、CLASS-METHODS语句中使用FOR EVENT OF选项定义event handler时,事件的输出参数就成了event handler的输入参数了,并且参数名要一致。非静态事件声明中除了明确使用EXPORTING定义的输出外,每个实例事件其实还有一个隐含的输出参数sender,这是一个引用类型的输出参数,当使用RAISE EVENT语句触发一个事件时,触发源(事件源)的对象就会分配给这个sender引用,但是静态事件没有隐含参数sender。
INTERFACE window. "窗口事件接口
EVENTS: minimize EXPORTING VALUE(status) TYPE i,"最小化事件
maximize EXPORTING VALUE(status) TYPE i,"最大化事件
restore EXPORTING VALUE(status) TYPE i."重置大小事件
ENDINTERFACE.
CLASS dialog_window DEFINITION. "窗口事件实现
PUBLIC SECTION.
INTERFACES window.
ENDCLASS.
INTERFACE window_handler. "窗口事件处理器接口
METHODS: minimize_window FOR EVENT window~minimize OF dialog_window
IMPORTING status sender, "事件处理器方法参数要与事件接口定义中的一致
maximize_window FOR EVENT window~maximize OF dialog_window
IMPORTING status sender,
restore FOR EVENT window~restore OF dialog_window
IMPORTING status sender.
ENDINTERFACE.
触发事件
这一步相当于Java中观察者模式中,调用被观察者Observable的notifyObservers()方法
RAISE EVENT evt [EXPORTING p1 = a1 p2 = a2 ...].
该语句只能在定义evt事件的同一类或子类,或接口实现方法中进行调用,即与evt事件的EVENTS 、CLASS-EVENTS定义语句所在类或子类,或接口实现方法进行调用。
当事件evt触发时,所有通过SET HANDLER语句进行注册过的event handlers事件处理器方法都将会被执行,执行的顺序依赖于注册的顺序。在执行事件处理器方法时,RAISE EVENT所在方法会暂停执行,而是待事件处理器方法执行完后,才会继续执行RAISE EVENT后面的语句。
EXPORTING:指定了该处理器方法的输入参数,此参数与事件evt声明EVENTS、CLASS-EVENTS语句中的输出参数对应(通过名称p1、p2…对应),a1、a2…为实参数,p1、p2…形参,并且是传值方式
避免无穷的事件递归触发,event handlers事件处理器方法中内嵌RAISE EVENT最多只能是63层
当实例事件触发时,如果在event handler事件处理器声明语句中指定了形式参数sender,则会自动接收将触发对象(事件源),但不能在RAISE EVENT …EXPORTING语句中明确指定,它会自动传递(如果是静态事件,则不会传递sender参数)
注:非静态事件只能在非静态方法中触发,而不能在静态方法中触发;而静态事件即可在静态也可在非静态方法中进行触发
CLASS c1 DEFINITION.
PUBLIC SECTION.
EVENTS e1 EXPORTING value(p1) TYPE string value(p2) TYPE I OPTIONAL.
METHODS m1.
ENDCLASS.
CLASS c1 IMPLEMENTATION.
METHOD m1.
...
RAIS EEVENT e1 EXPORTING p1 = '...'.
...
ENDMETHOD.
ENDCLASS.
事件处理器(方法)Event Handler
这一步相当于Java中观察者模式中,为观察者Observer定义的update()方法
事件处理器是一种使用METHODS... FOR EVENT ... OF选项定义的特殊的方法,并不直接使用CALL METHOD语句进行调用,而是使用RAISE EVENT语句来进行触发调用。
METHODS meth [ABSTRACT|FINAL]
FOR EVENT evt OF {class|ifac}
[IMPORTING p1 p2 ... [sender]].
CLASS-METHODS meth
FOR EVENT evt OF {class|intf}
[IMPORTING p1 p2 ...[sender]].
IMPORTING p1 p2中的p1、p2是class或ifac中EVENTS、CLASS-EVENTS语句中EXPORTING选项所定义的输出参数名,且这里的参数p1 p2不能使用TYPE、LIKE、OPTIONAL、DEFAULT 来修饰(因为已经在事件定义时进行说明了)
sender用来接收事件源,即触发(调用)此事件处理方法的对象,即为class、ifac相关实例对象。如果evt为静态事件,则不能使用sender参数
静态或非静态事件处理方法都可以处理静态或非静态事件,事件处理方法与事件的静态与否没有关系
CLASS picture DEFINITION.
PUBLIC SECTION.
METHODS handle_double_click FOR EVENT picture_dblclick OF cl_gui_picture
IMPORTING mouse_pos_x mouse_pos_y sender.
ENDCLASS.
CLASS picture IMPLEMENTATION.
METHOD handle_double_click.
...
ENDMETHOD.
ENDCLASS.
=================================================
CLASS dialog_box DEFINITION.
PUBLIC SECTION.
METHODS constructor.
...
PRIVATE SECTION.
CLASS-DATA open_boxes TYPE i.
CLASS-METHODS close_box FOR EVENTcloseOF cl_gui_dialogbox_container
IMPORTING sender.
...
ENDCLASS.
CLASS dialog_box IMPLEMENTATION.
METHOD constructor.
... " create a dialogbox
open_boxes = open_boxes + 1.
ENDMETHOD.
METHOD close_box
... " close the dialogbox referred by sender
open_boxes = open_boxes - 1.
ENDMETHOD.
ENDCLASS.
注册事件处理器
这一步相当于Java中观察者模式中,向被观察者对象Observable的ArrayList中添加监听器Observer
如果在某个类中定义了事件处理方法,则意味着该类的对象实例具有了处理该事件的能力。要使用事件处理方法能够对事件进行响应,必须在运行时为相关事件注册该方法。
实例事件处理器(方法)注册(注:被注册的方法只能是用来处理非静态事件的方法):
SET HANDLER handler1 handler2 ... FOR oref|{ALL INSTANCES} [ACTIVATION act].
静态事件处理器(方法)注册(注:被注册的方法只能是用来处理静态事件的方法):
SET HANDLER handler1 handler2 ... [ACTIVATION act].
oref:只将事件处理方法handler1 handler2注册到 oref 这一个对象,oref即为事件源对象(定义EVENT所在的类)。
ALL INSTANCES:将事件处理方法注册到所有的事件源实例中(EVENT定义所在类或子类,或其实现类),不管是在SET HANDLER语句之前还是之后创建的事件源对象,都会有效。
ACTIVATION act:表示是注册还是注销,如果act为 X ,则为注册,否则注销。
CLASS c1 DEFINITION.
PUBLIC SECTION.
EVENTS e1.
CLASS-EVENTS ce1.
ENDCLASS.
CLASS c2 DEFINITION INHERITING FROM c1.
PUBLIC SECTION.
EVENTS e2.
ENDCLASS.
CLASS c3 DEFINITION.
PUBLIC SECTION.
CLASS-METHODS h1 FOR EVENT e1 OF c1.
METHODS: h2 FOR EVENT e2 OF c2,
h3 FOR EVENT ce1 OF c1.
ENDCLASS.
...
DATA: trigger TYPE REF TO c2,
handler TYPE REF TO c3.
...
SET HANDLER: c3=>h1 handler->h2 FOR trigger, 由于h1、h2两个处理方法分别是用来处理非静态事件e1、e2的,所以只能采用实例注册方式
handler->h3. h3处理方法是用来处理静态事件ce1的,所以采用静态注册方式
====================================================================
"事件源
CLASS vehicle DEFINITION INHERITING FROM object.
PUBLIC SECTION.
"实例事件
EVENTS: too_fast EXPORTING value(p1) TYPE i. "事件对象
"静态事件
CLASS-EVENTS: too_fast_static EXPORTING value(p1) TYPE i.
METHODS: accelerate,show_speed IMPORTING msg TYPE string.
CLASS-METHODS: static_meth.
PROTECTED SECTION.
CLASS-DATA speed TYPE i .
ENDCLASS.
CLASS vehicle IMPLEMENTATION.
METHOD accelerate."非静态方法触发事件
"触发实例事件
RAISE EVENT too_fast EXPORTING p1 = speed .
"触发静态事件
RAISE EVENT too_fast_static EXPORTING p1 = speed .
ENDMETHOD.
METHOD static_meth."静态方法触发事件
"静态方法不能确发实例事件
"RAISE EVENT too_fast EXPORTING p1 = speed .
"触发静态事件
RAISE EVENT too_fast_static EXPORTING p1 = speed .
ENDMETHOD.
METHOD show_speed.
WRITE: / 'show_speed: ' ,msg.
ENDMETHOD.
ENDCLASS.
"监听器
CLASS handler DEFINITION.
PUBLIC SECTION.
METHODS:"实例处理方法
"事件处理方法的定义,参数一定要与事件定义时的参数名一致,sender为事件源
handle_1 FOR EVENT too_fast OF vehicle IMPORTING p1 sender,
"如果省略了sender,则 handle_2 不能访问sender对象
handle_2 FOR EVENT too_fast OF vehicle IMPORTING p1 ,
"注意:静态事件不会传递 sender 参数
handle_11 FOR EVENT too_fast_static OF vehicle IMPORTING p1.
CLASS-METHODS:"静态处理方法
handle_3 FOR EVENT too_fast OF vehicle IMPORTING p1 sender,
handle_4 FOR EVENT too_fast OF vehicle IMPORTING p1,
handle_33 FOR EVENT too_fast_static OF vehicle IMPORTING p1.
ENDCLASS.
CLASS handler IMPLEMENTATION.
METHOD handle_1.
sender->show_speed( msg = 'handle_1' ).
WRITE: / 'handle_1:' , p1.
ENDMETHOD.
METHOD handle_2.
"不能访问sender对象
"sender->show_speed( ).
WRITE: / 'handle_2:' , p1.
ENDMETHOD.
METHOD handle_11.
WRITE: / 'handle_11:' , p1.
ENDMETHOD.
METHOD handle_3.
sender->show_speed( msg = 'handle_3' ).
WRITE: / 'handle_3:' , p1.
ENDMETHOD.
METHOD handle_4.
WRITE: / 'handle_4:' , p1.
ENDMETHOD.
METHOD handle_33.
WRITE: / 'handle_33:' , p1.
ENDMETHOD.
ENDCLASS.
DATA: o_vehicle TYPE REF TO vehicle,
o_vehicle2 TYPE REF TO vehicle,
o_handler TYPE REF TO handler.
START-OF-SELECTION.
CREATE OBJECT: o_vehicle,o_vehicle2,o_handler.
"实例事件处理方法注册:只对类实例方法触发事件有效
SET HANDLER o_handler->handle_1 FOR o_vehicle.
SET HANDLER o_handler->handle_2 FOR ALL INSTANCES."只处理o_vehicle实例触发的事件
SET HANDLER handler=>handle_3 FOR ALL INSTANCES."处理vehicle类所有实例触发的事件
SET HANDLER handler=>handle_4 FOR ALL INSTANCES.
"静态事件处理方法注册:对类静态方法与类实例方法触发事件均有效
SET HANDLER o_handler->handle_11.
SET HANDLER handler=>handle_33.
CALL METHOD o_vehicle->accelerate.
SKIP.
CALL METHOD o_vehicle2->accelerate.
SKIP.
CALL METHOD vehicle=>static_meth.