通過接口標準化ABAP OO開發
本文是對接口編程的討論,希望能對年輕的開發者有所幫助。
要點:
- 通過接口對類方法進行更高層的抽象
- 接口使代碼清晰易讀
- 接口使你可以創建模擬對象(Mockup Object)以提高代碼的可測試性
- 幫助實現SOLID原則
- 可以在不使用RTTS和類型轉換的前提下使用多種類的不同實例。
因為在學習ABAP之前,我曾經學習過其它面向對象語言,因此我很糾結於ABAP中不存在的一個特性——重載方法(overload)。
也許你會問,重載是什麽?
重載就是函數或者方法有相同的名稱,但是參數列表和實現不相同的情形。
沒有了重載,在某種程度上,類也許會變的過大,並且難以追蹤那些有著相似行為但是名字不同的方法。
接口不提供重載能力,但是通過限制名字不同但是功能相近的方法的數量,接口可以整理和簡化你的代碼。
本文鏈接:http://www.cnblogs.com/hhelibeb/p/8919767.html
英文原文:Using interfaces to standardize your ABAP OO Development
簡介
在ABAP中類的繼承是單一繼承(每個類只能有一個父類),接口實現可以有多個。
例如,上圖中的LCL_Child_Class繼承LCL_Parent_Class中所有的非私有變量、方法、類型和常量,並且必須實現LINF_Utility和LINF_Saver接口中所有的功能。
為了解釋接口的定義,我將使用個“不怎麽專業”的描述——它是一個類似於類的實體,不包含所聲明的方法的任何具體實現,但是它可能包含常量、類型和變量。接口無法被初始化。
默認情況下接口的所有方法都必須被實現——這是面向對象編程中的一個通常的強制規則。不能允許接口方法的實現變得可選擇,但是這不屬於本文的討論範圍,所以不會展開論述。
(譯註:原文評論指出,在ABAP中,可以使用DEFAULT IGNORE|FAIL附加項指定一個可選的接口方法,雖然好像並沒有什麽用)
“真實”用例
設想下我們有個程序,需要從多種數據源獲取數據並更新到表SFLIGHT:
- Excel上傳
- RFC上傳
- 在程序運行期間上傳修改和插入的行
當然我們可以在該清單中添加ADBC源、經由HTTP客戶端對象抓取的JSON/XML源等,但是我只是想介紹下要點,沒必要窮舉所有例子。
同時,因為本文只是對可能性的表述,因此我不會創建一個能真正工作的程序。
聲明接口
我們將創建2個接口,不過在這個例子裏只有一個是真實需要的。
第一個是最重要的,我命名它為linf_sflight_career,因為這是個用於EXCEL、RFC和本地表運輸(carrier)的本地接口,在本地類中實現。
interface linf_sflight_carrier. types: tt_sflight type standard table of sflight with default key, st_sflight type sorted table of sflight with non-unique key mandt carrid connid, ht_sflight type hashed table of sflight with unique key mandt carrid connid fldate. methods: "! Returns hashed table SFLIGHT contents "! @parameter r_sflight | get_hashed_records returning value(r_sflight) type ht_sflight, "! Returns sorted table SFLIGHT contents "! @parameter r_sflight | get_sorted_records returning value(r_sflight) type st_sflight, "! Returns standard table SFLIGHT contents "! @parameter r_sflight | get_standard_records returning value(r_sflight) type tt_sflight. endinterface.
接口包含不同的表類型和三個方法,將會在EXCEL、RFC和表運輸的類中實現。
下個接口由負責保存數據到數據庫的類實現:
interface linf_sflight_saver. constants: "! Table lock types begin of lock_types, exclusive type enqmode value ‘E‘, end of lock_types. constants: "! Scopes for table lock begin of scope_range, _2 type char01 value ‘2‘, end of scope_range. constants: _sflight type tablename value ‘SFLIGHT‘. methods: "! Save data from carrier object to SFLIGHT table "! @parameter i_carrier | Carrier object save_data importing i_carrier type ref to linf_sflight_carrier. endinterface.
在這裏,你也許會問,為什麽我們需要這麽多類來完成一個很簡單的工作?為什麽我們不利用相似的類繼承或者是單個類來實現目的?
答案是顯然的:SOLID。如果你想要知道關於它的更多信息,可以留言回復,我將創建另一篇博客單獨講這一話題。
回到主題——接下來是類:
class lcl_excel_carrier definition. public section. interfaces: linf_sflight_carrier. aliases: tt_sflight for linf_sflight_carrier~tt_sflight, st_sflight for linf_sflight_carrier~st_sflight, ht_sflight for linf_sflight_carrier~ht_sflight, get_hashed_records for linf_sflight_carrier~get_hashed_records, get_sorted_records for linf_sflight_carrier~get_sorted_records, get_standard_records for linf_sflight_carrier~get_standard_records. protected section. private section. data: standard_sflight type tt_sflight, sorted_sflight type st_sflight, hashed_sflight type ht_sflight. endclass. class lcl_excel_carrier implementation. method get_hashed_records. r_sflight = hashed_sflight. endmethod. method get_sorted_records. r_sflight = sorted_sflight. endmethod. method get_standard_records. r_sflight = standard_sflight. endmethod. endclass. class lcl_rfc_carrier definition. public section. interfaces: linf_sflight_carrier. aliases: tt_sflight for linf_sflight_carrier~tt_sflight, st_sflight for linf_sflight_carrier~st_sflight, ht_sflight for linf_sflight_carrier~ht_sflight, get_hashed_records for linf_sflight_carrier~get_hashed_records, get_sorted_records for linf_sflight_carrier~get_sorted_records, get_standard_records for linf_sflight_carrier~get_standard_records. protected section. private section. data: standard_sflight type tt_sflight, sorted_sflight type st_sflight, hashed_sflight type ht_sflight. endclass. class lcl_rfc_carrier implementation. method get_hashed_records. r_sflight = hashed_sflight. endmethod. method get_sorted_records. r_sflight = sorted_sflight. endmethod. method get_standard_records. r_sflight = standard_sflight. endmethod. endclass. class lcl_table_carrier definition. public section. interfaces: linf_sflight_carrier. aliases: tt_sflight for linf_sflight_carrier~tt_sflight, st_sflight for linf_sflight_carrier~st_sflight, ht_sflight for linf_sflight_carrier~ht_sflight, get_hashed_records for linf_sflight_carrier~get_hashed_records, get_sorted_records for linf_sflight_carrier~get_sorted_records, get_standard_records for linf_sflight_carrier~get_standard_records. protected section. private section. data: standard_sflight type tt_sflight, sorted_sflight type st_sflight, hashed_sflight type ht_sflight. endclass. class lcl_table_carrier implementation. method get_hashed_records. r_sflight = hashed_sflight. endmethod. method get_sorted_records. r_sflight = sorted_sflight. endmethod. method get_standard_records. r_sflight = standard_sflight. endmethod. endclass.
上面的類有著相同的功能,但是根據具體的運輸目的,完整的實現類會有某些特定的方法(比如從raw數據中過濾、檢索數據等等)。
所有運輸類需要實現linf_sflight_carrier——由此我們不再不得不在每個類中定義所有的方法了。不過,我使用aliases關鍵字增加了別名,以提高代碼的可讀性。
我們下一個將要創建的類是數據庫保存者,名字是lcl_database_saver:
class lcl_database_saver definition. public section. interfaces: linf_sflight_saver. aliases: lock_types for linf_sflight_saver~lock_types, scope_range for linf_sflight_saver~scope_range, save_data for linf_sflight_saver~save_data, _sflight for linf_sflight_saver~_sflight. protected section. private section. methods: "! Creates table lock key for database lock "! @parameter i_sflight_ref | Reference to SFLIGHT table line "! @parameter r_varkey | Varkey returned create_varkey importing i_sflight_ref type ref to sflight returning value(r_varkey) type vim_enqkey, "! Locks table using passed varkey "! @parameter i_varkey | Table lock key "! @parameter i_tabname | Table name "! @parameter r_subrc | Information on lock creation. 0 = okay lock_table_line importing i_varkey type vim_enqkey i_tabname type tablename default _sflight returning value(r_is_locked) type abap_bool, "! Unlocks locked table line "! @parameter i_varkey | Table lock key "! @parameter i_tabname | Table name unlock_table_line importing i_varkey type vim_enqkey i_tabname type tablename default _sflight. endclass. class lcl_database_saver implementation. method save_data. loop at i_carrier->get_standard_records( ) reference into data(standard_line). data(varkey) = create_varkey( standard_line ). if lock_table_line( i_varkey = varkey ). modify sflight from standard_line->*. unlock_table_line( exporting i_varkey = varkey ). endif. endloop. endmethod. method lock_table_line. call function ‘ENQUEUE_E_TABLEE‘ exporting mode_rstable = lock_types-exclusive " Lock mode for table RSTABLE tabname = i_tabname " 01th enqueue argument varkey = i_varkey " 02th enqueue argument _scope = scope_range-_2 exceptions foreign_lock = 1 system_failure = 2 others = 3. r_is_locked = xsdbool( sy-subrc = 0 ). endmethod. method unlock_table_line. call function ‘DEQUEUE_E_TABLEE‘ exporting mode_rstable = lock_types-exclusive " Lock mode for table RSTABLE tabname = i_tabname " 01th enqueue argument varkey = i_varkey " 02th enqueue argument _scope = scope_range-_2. endmethod. method create_varkey. r_varkey = |{ i_sflight_ref->mandt }{ i_sflight_ref->carrid }{ i_sflight_ref->connid }{ i_sflight_ref->fldate }|. endmethod. endclass.
最後,運行例子:
initialization. data(excel_carrier) = new lcl_excel_carrier( ). data(rfc_carrier) = new lcl_rfc_carrier( ). data(database_saver) = new lcl_database_saver( ). try. database_saver->save_data( i_carrier = excel_carrier ). catch cx_sy_assign_cast_illegal_cast. catch cx_sy_assign_cast_unknown_type. catch cx_sy_assign_cast_error. endtry. try. database_saver->save_data( i_carrier = rfc_carrier ). catch cx_sy_assign_cast_illegal_cast. catch cx_sy_assign_cast_unknown_type. catch cx_sy_assign_cast_error. endtry.
如你所見,通過把抽象部分移動到接口層面,我們可以確保任何實現了linf_sflight_carrier接口的類可以傳輸給saver方法並且被正確處理。
另一個該實現的優點是可以快速簡單地創建模擬對象來進行單元測試。可測試的代碼即更好的代碼。
這就是本文的全部內容了,願你喜歡??。
總結
- 接口描述了一個會被類承諾提供的功能集。這是一個公共方法集。
- 接口中不含代碼,只有公共方法和數據聲明
- 任何實現了接口的類必須實現接口的承諾。例如,為每個這些公共方法寫代碼。
- 一個類也許會實現多個接口,這是ABAP中的多繼承。
- 而子類與父類間的關系是“是”的關系(例如,鴨子是鳥),接口則表達了一種“有”的關系(如“類XYZ有一個用於發送郵件的方法”)。
- 通用的OO原則是傾向於組合(composition)而非繼承(inheritance)。例如,在可能的情況下,不是使用子類而是使用實現接口的方式達到目的。
- 一個真實世界的例子是,某個人平日裏是會計,而業余時間是救火誌願者。消防隊只關心他救火的能力,相應地雇主則只在意他的會計能力。
- 在編程的世界裏,類變量可以被定義為接口,然後任何實現該接口的類都可以被傳入(需要這個接口的位置)。調用程序只能訪問接口的方法,並不知道該類還有進行其它種類行為的能力。
通過接口標準化ABAP OO開發