類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.