建指南:從設(shè)計到實踐)
1. 項目概述為什么需要全局類在SAP ABAP開發(fā)中對象導(dǎo)向編程OO ABAP已經(jīng)成為構(gòu)建復(fù)雜、可復(fù)用、易于維護(hù)業(yè)務(wù)邏輯的主流范式。而全局類正是這個范式中的核心基石。你可以把它想象成一個“公共工具箱”一旦創(chuàng)建并激活它就可以被整個SAP系統(tǒng)內(nèi)的任何程序、函數(shù)模塊、Web服務(wù)或其他類所調(diào)用。這與只能在單一程序內(nèi)使用的本地類形成了鮮明對比。我最初接觸全局類時也常常困惑為什么不用函數(shù)模塊或者子程序但隨著項目復(fù)雜度提升我深刻體會到全局類的價值。它不僅僅是代碼的容器更是封裝數(shù)據(jù)和行為、實現(xiàn)繼承與多態(tài)、定義清晰接口的載體。比如當(dāng)你需要定義一個跨多個模塊如SD、MM、FI使用的通用業(yè)務(wù)對象如“客戶主數(shù)據(jù)處理器”或者開發(fā)一個供Fiori應(yīng)用調(diào)用的OData服務(wù)背后的業(yè)務(wù)邏輯時全局類幾乎是唯一的選擇。它讓代碼結(jié)構(gòu)更清晰職責(zé)更分明也極大地促進(jìn)了團(tuán)隊協(xié)作和代碼復(fù)用。2. 全局類創(chuàng)建前的核心設(shè)計考量在動手敲下SE24事務(wù)碼之前有幾個關(guān)鍵的設(shè)計決策需要想清楚。這一步的思考深度直接決定了你創(chuàng)建的類是“一次性用品”還是“傳家寶”。2.1 明確類的職責(zé)與邊界一個類應(yīng)該只做一件事并且把它做好。這是面向?qū)ο笤O(shè)計的基本原則。在SAP的業(yè)務(wù)背景下這意味著你需要仔細(xì)分析你的業(yè)務(wù)需求。是實體類還是工具類實體類通常對應(yīng)一個業(yè)務(wù)對象如CL_SALES_ORDER銷售訂單類、CL_MATERIAL物料類。它封裝了該對象的數(shù)據(jù)屬性和行為方法。例如一個銷售訂單類可能有order_number、customer_id等屬性以及create()、change()、display()等方法。工具類/輔助類提供通用的、與特定業(yè)務(wù)對象無關(guān)的功能如CL_STRING_UTILITIES字符串工具、CL_DATE_CALCULATOR日期計算。這類類通常只包含靜態(tài)方法CLASS-METHODS無需實例化即可調(diào)用。我見過不少新手開發(fā)者把各種不相關(guān)的功能塞進(jìn)一個類里導(dǎo)致后期維護(hù)時牽一發(fā)而動全身。我的經(jīng)驗是如果發(fā)現(xiàn)一個類的方法列表長得需要滾動好幾屏或者類的描述難以用一句話概括那很可能就需要拆分了。2.2 設(shè)計清晰的公共接口類的公共接口PUBLIC SECTION是它與外部世界通信的契約。設(shè)計時需要遵循“最小暴露原則”只將必要的方法和屬性設(shè)為公有。方法設(shè)計每個公共方法都應(yīng)該有明確、單一的功能。參數(shù)不宜過多通常超過5個就應(yīng)考慮是否能用結(jié)構(gòu)體或類來封裝。務(wù)必為每個參數(shù)使用VALUE、REFERENCE或RETURNING明確其傳遞方式這是ABAP區(qū)別于其他語言的一個細(xì)節(jié)但至關(guān)重要。 不推薦的模糊設(shè)計 METHODS process_data IMPORTING iv_data TYPE string iv_flag TYPE abap_bool. 推薦的清晰設(shè)計 METHODS calculate_net_amount IMPORTING is_item TYPE ty_sales_item RETURNING VALUE(rv_net) TYPE netwr RAISING cx_calculation_error.屬性設(shè)計除非是常量否則盡量避免將屬性直接放在PUBLIC SECTION。更佳實踐是通過GET和SET方法即Getter和Setter來訪問和修改私有屬性。這為你后續(xù)添加數(shù)據(jù)驗證、日志記錄等邏輯留下了空間。2.3 繼承與接口的運用策略SAP ABAP支持單繼承和多接口實現(xiàn)。這是實現(xiàn)代碼復(fù)用和定義多態(tài)行為的關(guān)鍵。何時使用繼承當(dāng)你要創(chuàng)建的新類是某個現(xiàn)有類的特殊化版本并且存在明確的“是一個is-a”關(guān)系時。例如CL_PREMIUM_CUSTOMERVIP客戶繼承自CL_CUSTOMER普通客戶。繼承意味著子類擁有父類的所有屬性和方法。注意在SAP標(biāo)準(zhǔn)環(huán)境中繼承自SAP提供的全局類特別是以CL_開頭的需要格外謹(jǐn)慎因為父類的升級可能會影響你的子類。何時使用接口當(dāng)你想定義一組必須實現(xiàn)的方法契約而不關(guān)心具體實現(xiàn)類時。接口定義了“能做什么”而不規(guī)定“是什么”。例如你可以定義一個ZIF_PRINTABLE接口包含print方法。那么無論是銷售訂單、發(fā)票還是報表只要實現(xiàn)了這個接口就可以被同一個打印程序處理。接口是實現(xiàn)松耦合設(shè)計的利器。實操心得在SAP項目中我個人的傾向是“優(yōu)先使用組合與接口而非繼承”。除非關(guān)系非常穩(wěn)定和明確否則繼承帶來的耦合度往往在后期成為負(fù)擔(dān)。通過接口來定義行為用組合來復(fù)用功能通常會讓系統(tǒng)更靈活。3. 分步詳解在SE24中創(chuàng)建你的第一個全局類理論鋪墊完畢我們進(jìn)入實戰(zhàn)環(huán)節(jié)。SAP提供了強大的類構(gòu)建器事務(wù)碼SE24來可視化地創(chuàng)建和管理全局類。3.1 創(chuàng)建類的基本框架啟動與命名輸入事務(wù)碼SE24在初始界面輸入你的類名。SAP的命名約定建議ZCL_或YCL_開頭表示自定義類后接有意義的描述如ZCL_SALES_ORDER_MANAGER。點擊“創(chuàng)建”選擇“類”類型點擊創(chuàng)建按鈕。填寫屬性頁描述用簡明語言說明這個類的用途。這很重要未來其他開發(fā)者包括六個月后的你自己靠它快速理解。繼承如果你要繼承某個現(xiàn)有類在此處輸入父類名。接口在“接口”頁簽添加該類需要實現(xiàn)的所有接口。Instantiation實例化屬性。通常選擇PUBLIC可被任何地方實例化。如果你希望這個類只能通過靜態(tài)方法調(diào)用如工具類則選擇PRIVATE。Final勾選此項表示該類不能被繼承。對于工具類或你認(rèn)為設(shè)計已完備的類可以勾選以提高系統(tǒng)性能并防止被誤繼承。3.2 定義類的各個組成部分創(chuàng)建后你會進(jìn)入類構(gòu)建器的主界面。這里是我們工作的核心區(qū)域。3.2.1 屬性Attributes屬性代表對象的狀態(tài)。切換到“屬性”頁簽。實例屬性屬于某個具體對象。例如每個ZCL_SALES_ORDER對象都有自己的mv_order_number。靜態(tài)屬性屬于類本身所有對象共享。例如一個記錄創(chuàng)建訂單總數(shù)的gv_order_count??梢娦訮UBLIC通常只放常量CONSTANTS。PROTECTED子類可以訪問。PRIVATE僅類自身的方法可以訪問。絕大多數(shù)實例屬性都應(yīng)設(shè)為PRIVATE通過方法來訪問。3.2.2 方法Methods方法是對象的行為。切換到“方法”頁簽。添加方法輸入方法名如CREATE_FROM_DATA。設(shè)置可見性同上。定義參數(shù)這是關(guān)鍵步驟。雙擊方法名進(jìn)入?yún)?shù)定義界面。導(dǎo)入?yún)?shù)IMPORTING方法執(zhí)行所需的數(shù)據(jù)。導(dǎo)出參數(shù)EXPORTING方法執(zhí)行后輸出的數(shù)據(jù)。變更參數(shù)CHANGING既輸入又輸出的參數(shù)慎用容易造成混淆。返回參數(shù)RETURNING單一的輸出結(jié)果與方法功能強相關(guān)時使用。異常EXCEPTIONS / RAISING定義方法可能拋出的異常。推薦使用基于類的異常RAISING它比傳統(tǒng)的EXCEPTIONS更強大能傳遞更多錯誤信息。編寫實現(xiàn)代碼參數(shù)定義好后點擊“源代碼”按鈕進(jìn)入ABAP編輯器編寫方法的具體邏輯。3.2.3 事件Events和類型Types事件用于實現(xiàn)類間的松耦合通信發(fā)布-訂閱模式。比如訂單創(chuàng)建完成后可以觸發(fā)一個ORDER_CREATED事件讓其他關(guān)注此事件的類如日志記錄類、消息通知類自動執(zhí)行相應(yīng)操作。類型你可以在類內(nèi)部定義只在類范圍內(nèi)使用的數(shù)據(jù)類型如結(jié)構(gòu)體、內(nèi)表類型。這有助于封裝避免在全局?jǐn)?shù)據(jù)字典中創(chuàng)建過多僅本類使用的類型。3.3 激活與首次測試所有元素定義完成后點擊工具欄上的“激活”按鈕CtrlF3。激活時系統(tǒng)會進(jìn)行語法檢查和一致性檢查。激活后立即在SE24界面使用“測試”環(huán)境F8進(jìn)行快速驗證。在測試界面你可以創(chuàng)建類的實例對象。調(diào)用公共方法并輸入測試參數(shù)。觀察輸出結(jié)果和系統(tǒng)狀態(tài)確保基本功能符合預(yù)期。注意事項在測試調(diào)用方法時如果方法有RAISING聲明測試工具會自動生成代碼來捕獲異常。務(wù)必檢查在異常情況下類的行為是否如你所愿。4. 核心環(huán)節(jié)實現(xiàn)一個銷售訂單輔助類的完整示例讓我們通過一個相對完整的例子將上述理論串聯(lián)起來。假設(shè)我們需要一個管理銷售訂單折扣的輔助類。4.1 類設(shè)計與定義類名ZCL_SALES_DISCOUNT_CALCULATOR描述用于計算銷售訂單行項目的各類折扣和最終凈價。實例化PUBLIC因為可能需要為不同訂單保留不同上下文但本例也可設(shè)計為靜態(tài)工具類。不繼承不實現(xiàn)接口初期。屬性定義PRIVATE SECTIONPRIVATE SECTION. DATA: mv_kunnr TYPE kunnr, “ 客戶編號 mv_waerk TYPE waerk, “ 貨幣碼 mt_discount_conditions TYPE TABLE OF zty_discount_cond. “ 自定義的折扣條件內(nèi)表方法定義構(gòu)造函數(shù)CONSTRUCTOR用于初始化對象如傳入客戶和貨幣。METHODS constructor IMPORTING iv_kunnr TYPE kunnr iv_waerk TYPE waerk.主計算方法CALCULATE_NET_PRICE核心業(yè)務(wù)邏輯。METHODS calculate_net_price IMPORTING iv_gross_price TYPE netwr iv_material TYPE matnr iv_quantity TYPE menge_d EXPORTING ev_discount_amount TYPE netwr ev_net_price TYPE netwr RAISING zcx_discount_error.條件加載方法LOAD_DISCOUNT_CONDITIONS私有方法從數(shù)據(jù)庫或配置表讀取該客戶的折扣條件。METHODS load_discount_conditions PRIVATE.4.2 方法實現(xiàn)與編碼要點在CONSTRUCTOR的實現(xiàn)中除了賦值最好立即調(diào)用load_discount_conditions來加載數(shù)據(jù)確保對象創(chuàng)建后立即可用。CALCULATE_NET_PRICE方法是重點METHOD calculate_net_price. DATA: lv_discount_rate TYPE p DECIMALS 3. CLEAR: ev_discount_amount, ev_net_price. “ 1. 數(shù)據(jù)校驗 IF iv_gross_price 0 OR iv_quantity 0. RAISE EXCEPTION TYPE zcx_discount_error EXPORTING textid zcx_discount_errorinvalid_input. ENDIF. “ 2. 根據(jù)物料、客戶和數(shù)量從 mt_discount_conditions 中查找適用的折扣率 “ 這里假設(shè)有一個匹配邏輯可能是循環(huán)內(nèi)表查找 READ TABLE mt_discount_conditions WITH KEY material iv_material INTO DATA(ls_cond). IF sy-subrc 0. lv_discount_rate ls_cond-discount_rate. ELSE. “ 沒有特定物料折扣則查找客戶通用折扣 READ TABLE mt_discount_conditions WITH KEY material space customer mv_kunnr INTO ls_cond. IF sy-subrc 0. lv_discount_rate ls_cond-discount_rate. ENDIF. ENDIF. “ 3. 計算折扣金額和凈價 IF lv_discount_rate 0. ev_discount_amount iv_gross_price * iv_quantity * lv_discount_rate / 100. ENDIF. ev_net_price ( iv_gross_price * iv_quantity ) - ev_discount_amount. “ 4. 可以觸發(fā)一個‘價格已計算’的事件供其他監(jiān)聽者使用如果需要 RAISE EVENT price_calculated EXPORTING gross_price iv_gross_price net_price ev_net_price. ENDMETHOD.編碼心得異常處理像上面的數(shù)據(jù)校驗使用自定義異常類ZCX_DISCOUNT_ERROR比用MESSAGE語句更優(yōu)雅調(diào)用者可以決定如何彈窗、日志、轉(zhuǎn)換處理這個錯誤。數(shù)據(jù)訪問LOAD_DISCOUNT_CONDITIONS中訪問數(shù)據(jù)庫時要考慮性能。如果折扣條件不常變可以考慮加入緩存機制比如將內(nèi)表MT_DISCOUNT_CONDITIONS設(shè)為靜態(tài)屬性并在首次訪問時加載。事件使用PRICE_CALCULATED事件是可選的。但如果你設(shè)想未來可能有審計、實時監(jiān)控等需求預(yù)先定義事件會讓擴展變得非常簡單。5. 高級特性與集成應(yīng)用掌握了基礎(chǔ)創(chuàng)建后全局類的威力在于其可集成性。5.1 在ABAP程序、函數(shù)模塊及其他類中調(diào)用調(diào)用全局類非常簡單。DATA: lo_calculator TYPE REF TO zcl_sales_discount_calculator, lv_discount TYPE netwr, lv_net TYPE netwr. “ 創(chuàng)建對象 CREATE OBJECT lo_calculator EXPORTING iv_kunnr ‘100001’ iv_waerk ‘USD’. “ 調(diào)用方法 TRY. lo_calculator-calculate_net_price( EXPORTING iv_gross_price 100 iv_material ‘MAT001’ iv_quantity 5 IMPORTING ev_discount_amount lv_discount ev_net_price lv_net ). CATCH zcx_discount_error INTO DATA(lx_error). “ 處理異常例如顯示錯誤消息 MESSAGE lx_error-get_text( ) TYPE ‘E’. ENDTRY.在函數(shù)模塊或另一個類的方法中調(diào)用方式完全相同。這種一致性極大地降低了學(xué)習(xí)成本。5.2 作為RFC/BAPI或OData服務(wù)后端這是全局類在企業(yè)級應(yīng)用中的關(guān)鍵角色。RFC/BAPI你可以創(chuàng)建一個實現(xiàn)了BAPI接口的全局類。SAP的BAPI本質(zhì)上就是一些特殊的RFC函數(shù)模塊但用類來實現(xiàn)的BAPI結(jié)構(gòu)更清晰。在SE24中創(chuàng)建類時可以實現(xiàn)IF_BAPI_*等標(biāo)準(zhǔn)接口將你的業(yè)務(wù)邏輯包裝成標(biāo)準(zhǔn)BAPI供外部系統(tǒng)如.NET, Java應(yīng)用調(diào)用。OData服務(wù)SAP Gateway在SAP S/4HANA或通過SAP NetWeaver Gateway開發(fā)Fiori應(yīng)用時OData服務(wù)的DPC_EXT數(shù)據(jù)提供者擴展類就是一個全局類。你需要在GET_ENTITYSET、GET_ENTITY、CREATE_ENTITY等方法中調(diào)用你編寫的業(yè)務(wù)邏輯類如上面的ZCL_SALES_DISCOUNT_CALCULATOR來獲取和處理數(shù)據(jù)。業(yè)務(wù)邏輯類負(fù)責(zé)純粹的運算和規(guī)則DPC類負(fù)責(zé)HTTP協(xié)議和數(shù)據(jù)映射這是一種很好的分層架構(gòu)。5.3 類的持久化與工廠模式有時我們需要保存和恢復(fù)一個復(fù)雜對象的狀態(tài)。雖然SAP有持久化對象服務(wù)但在自定義開發(fā)中更常見的是實現(xiàn)一個“序列化/反序列化”方法將對象關(guān)鍵屬性存儲到數(shù)據(jù)庫透明表中或轉(zhuǎn)換成JSON/XML字符串。另一種高級模式是“工廠模式”。你可以創(chuàng)建一個ZCL_OBJECT_FACTORY類它的靜態(tài)方法根據(jù)輸入?yún)?shù)決定創(chuàng)建并返回哪一種具體的業(yè)務(wù)對象。這完全隱藏了對象創(chuàng)建的復(fù)雜性。CLASS-METHODS create_document IMPORTING iv_doc_type TYPE string RETURNING VALUE(ro_doc) TYPE REF TO zif_document RAISING cx_factory_error.在這個方法里可以用CASE iv_doc_type來判斷是創(chuàng)建銷售訂單、采購訂單還是發(fā)票對象。6. 常見問題、調(diào)試與性能優(yōu)化實錄即使設(shè)計得再完美在實際開發(fā)和運行中也會遇到各種問題。6.1 創(chuàng)建與激活階段的典型錯誤問題現(xiàn)象可能原因解決方案激活時報“語法錯誤”方法實現(xiàn)代碼有ABAP語法錯誤。仔細(xì)檢查編輯器中的紅色下劃線提示常見于字符串引號不匹配、語句不完整等。激活時報“接口未實現(xiàn)”類聲明實現(xiàn)了某個接口但未在“方法”頁簽中為該接口的所有方法提供實現(xiàn)。在SE24的“方法”頁簽檢查接口方法是否都已存在。如果沒有需要手動添加并實現(xiàn)。激活時報“父類方法未重定義”繼承自父類并試圖重寫Redefine一個父類方法但簽名參數(shù)不一致。重定義方法時必須保持與父類方法完全相同的參數(shù)接口包括異常。測試時對象創(chuàng)建失敗類的實例化屬性為PRIVATE或PROTECTED但試圖在外部用CREATE OBJECT創(chuàng)建。檢查類的Instantiation屬性。如果是工具類應(yīng)通過靜態(tài)方法訪問而非創(chuàng)建對象。6.2 運行時調(diào)試與問題排查設(shè)置斷點在SE24中直接雙擊方法實現(xiàn)代碼的行號左側(cè)即可設(shè)置外部斷點。當(dāng)任何程序調(diào)用該方法時調(diào)試器都會在此停止。觀察屬性值在調(diào)試器變量查看器中展開ME引用可以查看當(dāng)前對象的所有私有、保護(hù)、公有屬性的值。這是理解對象狀態(tài)的關(guān)鍵。調(diào)用棧分析當(dāng)異常發(fā)生時查看調(diào)用棧Call Stack可以清晰地看到是哪個外部程序、通過什么路徑調(diào)用了你的類方法并傳遞了什么參數(shù)這對于定位誰在用錯誤數(shù)據(jù)調(diào)用你非常有用。動態(tài)類型檢查如果方法返回一個抽象類的引用如REF TO zif_document但在調(diào)試時想知道具體是哪個子類可以使用DESCRIBE OBJECT語句或直接觀察調(diào)試器中的動態(tài)類型信息。6.3 性能優(yōu)化注意事項全局類本身性能開銷極低但使用不當(dāng)會影響系統(tǒng)。避免在循環(huán)中頻繁創(chuàng)建/銷毀對象對象創(chuàng)建CREATE OBJECT有一定成本。如果需要在循環(huán)內(nèi)使用同一個功能應(yīng)在循環(huán)前創(chuàng)建對象循環(huán)內(nèi)重復(fù)使用。謹(jǐn)慎使用靜態(tài)屬性靜態(tài)屬性在內(nèi)存中只有一份所有用戶共享。如果用它存儲用戶相關(guān)數(shù)據(jù)會導(dǎo)致嚴(yán)重的數(shù)據(jù)混亂。靜態(tài)屬性通常只用于全局緩存、計數(shù)器等場景。數(shù)據(jù)庫訪問優(yōu)化類方法中的數(shù)據(jù)庫查詢SELECT要遵循ABAP性能優(yōu)化通用準(zhǔn)則使用主鍵、避免SELECT *、使用內(nèi)表代替逐行查詢等。對于頻繁讀取的配置數(shù)據(jù)考慮在類初始化時一次性加載到私有或靜態(tài)內(nèi)表中緩存。事件處理的性能影響事件是解耦的利器但一個事件被眾多監(jiān)聽者響應(yīng)時可能會拖慢主流程。對于性能關(guān)鍵路徑需要評估事件處理的必要性。6.4 版本管理與傳輸全局類像其他ABAP工作臺對象一樣被包含在傳輸請求Transport Request中。需要特別注意的是向下兼容性當(dāng)你修改一個已被廣泛使用的全局類的公共接口如刪除一個公共方法、改變其參數(shù)時所有調(diào)用它的程序在激活時都可能出現(xiàn)語法錯誤或運行時錯誤MESSAGE_TYPE_X。因此修改公共接口需極其謹(jǐn)慎最好通過添加新方法并標(biāo)記舊方法為過時DEPRECATED的方式來演進(jìn)。激活順序如果類A引用了類B如作為參數(shù)類型那么傳輸時類B必須先于或同時與類A被傳輸?shù)侥繕?biāo)系統(tǒng)否則類A的激活會失敗。創(chuàng)建全局類從理清設(shè)計思路開始到在SE24中一步步實現(xiàn)最后集成到復(fù)雜的應(yīng)用架構(gòu)中是一個系統(tǒng)工程。它要求開發(fā)者不僅掌握ABAP語法更要具備面向?qū)ο蟮脑O(shè)計思維。