1. 程式人生 > >6.3 SAP ABAP 開放封閉原則(OCP)- 摘自 《SAP ABAP面向物件程式設計:原則、模式及實踐》 6.3 SAP ABAP 開放封閉原則(OCP)- 摘自 《SAP ABAP面向物件程式設計:原則、模式及實踐》

6.3 SAP ABAP 開放封閉原則(OCP)- 摘自 《SAP ABAP面向物件程式設計:原則、模式及實踐》 6.3 SAP ABAP 開放封閉原則(OCP)- 摘自 《SAP ABAP面向物件程式設計:原則、模式及實踐》

6.3 SAP ABAP 開放封閉原則(OCP)- 摘自 《SAP ABAP面向物件程式設計:原則、模式及實踐》

 

6.3 開放封閉原則(OCP)

開閉原則(Open-Closed Principle, OCP)指的是,一個類或者模組,如果在業務修改或者功能需要擴充套件時,應儘可能保證只通過新新增程式碼,而不是修改原有程式碼的情況下完成。

 

該原則是Eiffel 語言的設計者,法國電腦科學家Meyer提出的,最開始的描述是:當向模組新增欄位或方法時,不可避免地需要對呼叫這個模組的程式進行修改,解決這問題的方法是採用依賴於面向物件繼承(特別是從實際父類進行的實現繼承)的方法,而不是直接修改原來的程式。

從20世紀90年代開始,OCP的原則依然被大家遵循,只是解決的方式變了。隨著抽象類和介面等方式的大量應用,開放封閉原則開始提倡採用抽象類或介面的方法,而不是實現繼承來解決問題。

解決方式是現存的介面或抽象類對於修改原有的方法和屬性是封閉的,但對新新增的方法和屬性則是開放和允許的。

 

為什麼要對原有的程式碼進行保護,對新加的程式碼開放呢?

所有的軟體和程式都有一個生命週期,當需求和業務發生擴充套件和更新時,需要更新軟體(修復缺陷和軟體重構時的更新除外),要儘可能保證軟體的基本框架不變,儘可能不修改現有的程式碼,而是新增新的實現程式碼,使得軟體具有好的穩定性和可維護性。

 

實現擴充套件而不修改原有程式碼的基礎就是採用介面或者抽象類等機制完成的。經過良好的定義,系統可以擁有一個相對穩定的抽象層,將業務行為下沉到具體的實現層中。

如果業務有擴充套件,可以將擴充套件的業務放到實現層中,只新增新的程式碼,不修改已有的程式碼,通過抽象層,將新的業務邏輯指定到新新增的業務實現層中。

如果業務有修改,無須對抽象層進行任何改動,新增實現程式碼,替換固定的實現層程式碼,而不會影響實現層其他模組的行為。

 

設計例項:

一個公司中有多種物料型別,每種物料都要根據物料型別列印不同的檢驗說明單據,這個檢驗說明是由一個非面向物件的程式來控制的,當公司中的物料型別增加一種時,IT部門就被要求為這個程式新增一種列印方法。IT部門每次修改這個列印程式後,都要為以前的每一種的列印都做一個回退測試,保證新加的程式碼沒有影響以往的業務。

如圖6-4所示,程式流程圖如下,當新增一種新物料型別時,程式就又多一組判斷:

 

 

 

 

程式碼如示例程式6.1所示。

"示例程式6.1

REPORT zrep_cls_021.

DATA gv_material_type TYPE mara-mtart.
DATA gv_material TYPE mara-matnr.

PARAMETERS: p_matnr LIKE mara-matnr.

START-OF-SELECTION.

SELECT SINGLE mtart FROM mara INTO gv_material_type
WHERE matnr = p_matnr.

IF sy-subrc = 0.

"型別判斷
IF ( gv_material_type EQ 'ROH') .
WRITE: / '此物料為原料,需檢查採購合同 '.
ELSEIF ( gv_material_type EQ 'FERT') .
WRITE: / '此物料為成品,需檢查銷售合同 '.
ENDIF.
ENDIF.

  

當每次公司出現一個新的物料型別時,就要新增一個針對這個新型別的相關檢驗的要求,我們就需要去修改這個IF ELSE程式碼新增一個新的邏輯,主程式就要每一次都進行修改,這就不符合開放封閉原則。

 

對於不斷變化的業務,如果我們每次修改原有的程式碼,除了要對增加的功能進行測試,對原有的功能還必須要做回退測試(Regression Test),保證新的邏輯沒有影響以前的業務邏輯。但如果軟體能夠做到僅增加新的程式碼,不修改,或基於一定的規則的修改原有程式碼,這樣就能保證工作量和程式碼出錯的機率大幅下降。這就是"開放-關閉"原則,即軟體擴充套件時只新增新程式碼而不是修改原有程式碼。

 

我們用面向物件的程式設計,並採用一個"改進的簡單工廠" 設計模式(也就是基於配置表的簡單工廠模式,具體結構見第7章第3節)來對系統進行重構,說明一下如何做到重構中的開放封閉原則。(例子中的這個程式因為太簡單,其實是沒必要進行面向物件重構的,但這個例子是足夠能說明實際業務中的重構過程和如何做到的遵循開放封閉原則。重構後,程式碼量會增加,但卻是十分值得的。)

 

如圖6-5所示,首先將業務抽象為三層,第一層為物料物件生成工廠類,相當於主程式中的操作者,用於建立物料類的物件例項。第二層為抽象的物料類,由物料物件生成工廠類直接呼叫。第三層是具體的物料類,代表具體的物料型別,可以不斷從抽象類中繼承和新增新的子類,具體的解釋如表6-2所示。

 

其中第二層抽象類隔離開了操作類和具體物料類,使得主程式可以不必修改,就可以處理新新增的業務邏輯。

 

 

程式和實體列表

解釋

第一層類:物料物件生成工廠類

一個類,用於建立(生產)物料類的物件例項。

第二層抽象類:抽象的物料類

一個類,抽象的物料類。

第三層類:具體的物料類

可以有多個類,每個代表一個具體的物料型別和相應的處理方法。

資料表:物料型別和相應具體物理類的表

表中記錄了SAP物料的型別和對應具體類的類名。

 

 

 

我們逐步介紹如何建立這些類和實體:

第一步,如圖6-6所示,建立抽象的物料類ZCL_MATERIAL,這是第二層的抽象類,將多樣的具體業務抽象為一種模式。設定類的型別為"Abstract"抽象類。

 

如圖6-7所示,建立一個屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼(MARA-MATNR)。

 

 

如圖6-8所示,設定方法PRINT_INSP_DESC,可見型別為Public,無引數。不必定義具體邏輯,該方法表明了物料類擁有列印檢驗說明的能力,但具體實現程式碼是由其子類各自實現的。

 

 

 

第二步,如圖6-9所示,建立第三層的抽象的子類。分別為"成品物料類"ZCL_MATERIAL_FIN,"原料物料類"ZCL_MATERIAL_RAW,這一層是最底層的實現類,代表了具體的實現方法,如果有新的業務新增時,也是在這一層次建立新的類。

 

建立成品物料類ZCL_MATERIAL_FIN,從抽象物料類繼承。

 

 

如圖6-10所示,子類自動繼承屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼。MARA-MATNR。

 

 

如圖6-11所示,重定義繼承來的方法PRINT_INSP_DESC。

 

如圖6-12所示,該方法是對應的成品物料類的具體操作方法,比如獲取固定資料,生成固定格式,併發送到相應的輸出印表機等。我們這裡就簡單的寫成"此物料為成品,需檢查銷售合同",用於代表現實中複雜的邏輯。

 

 

如圖6-13所示,建立原料物料類ZCL_MATERIAL_RAW,從抽象物料類繼承。

 

 

如圖6-14所示,自動繼承屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼。MARA-MATNR。

 

 

如圖6-15所示,重定義繼承來的方法PRINT_INSP_DESC。

 

 

如圖6-16所示,該方法是對應的原料物料類的具體操作方法,比如獲取採購或生產資料,生成固定格式的報表,併發送到相應的輸出印表機等。我們寫成"此物料為原料,需檢查採購合同",用於代表現實中複雜的邏輯。

 

 

第三步,如圖6-17所示,建立表ZINSP_DESC_T:

列SORT_NUM代表序號。

列MATERIAL_TYPE儲存著SAP標準的物料型別。

列CLASS_TYPE儲存著處理相應物料型別的相應的物料類類名(類名需要大寫)。然後參照"資料字典"章節,維護列的Domain,資料型別和維護表等工作。

 

 

如圖6-18所示,執行SM30,將SAP標準的物料型別"ROH原料型別",及其對應的處理類"ZCL_MATERIAL_RAW",物料型別"FERT成品型別",極其對應的處理類"ZCL_MATERIAL_FIN"。新增到表中。

在Java和.NET中,大多使用XML檔案來儲存類似的服務註冊,SAP ABAP中最為方便的就是用資料庫表。

 

 

第四步,如圖6-19所示,建立簡單工廠類ZCL_MATL_SPL_FACTORY,這是第一層的呼叫類,模式控制的核心程式碼定義在這一層 ,這個類將實現根據物料動態建立物料子類型別(也就是用於建立物料類物件的簡單類工廠),並實現多型的能力,多型在這個架構中將展現其威力。

 

類ZCL_MATL_SPL_FACTORY是普通的Public型別的非抽象類。

 

如圖6-20所示,設定方法GET_MATERIAL_OBJ,可見型別為Public,該方法是根據物料號碼,獲取物料型別,繼而獲得對應的物料類的名稱並建立類物件例項,方法可以是靜態型別方法(Static Method),即可以用類名來訪問方法(用=>符呼叫)。

 

如圖6-21所示,設定方法的輸入引數 IV_MATERIAL_ID,是傳入的物料號碼MARA-MATNR。

設定方法的返回引數 RO_MATERIAL,型別是抽象類物料物件型別ZCL_MATERIAL。方法將其例項化後作為返回值傳出,例項化的物件都是物料類的可例項化的子類物件,然後賦值給父類物料類物件進行上轉型,此處就是典型的多型的應用。

 

程式碼根據傳入的物料號碼,在標準表MARA中查詢出物料型別,然後根據物料型別去自定義表格中查詢對應的處理類,然後建立對應類物件例項作為返回的引數,程式碼如示例程式6.2所示,程式碼中的"動態建立類物件 ro_material,型別為登錄檔中的類的型別"即是按子類型別建立父類物件,即向上轉型的多型的實現。

 

"示例程式6.2

METHOD get_material_obj.

DATA: lv_material_type TYPE mara-mtart,
lv_material TYPE mara-matnr,
lv_class TYPE char30.

DATA:
exc_ref TYPE REF TO cx_root,
exc_text TYPE string,
lv_type_name TYPE string,
lv_method_name TYPE string.

"先根據物料號碼查詢物料型別
SELECT SINGLE mtart FROM mara INTO lv_material_type
WHERE matnr = iv_material_id.
IF sy-subrc = 0.
"在根據物料型別查詢登錄檔中對應的物料類的型別
SELECT SINGLE class_type FROM zinsp_desc_t INTO lv_class
WHERE material_type = lv_material_type.
ENDIF.
"如果有查詢記錄則開始動態建立類
IF sy-subrc = 0.
lv_type_name = lv_class. "類名必須大寫
TRANSLATE lv_type_name TO UPPER CASE.
TRY.
"動態建立類物件 ro_material,型別為登錄檔中的類的型別,
CREATE OBJECT ro_material TYPE (lv_type_name).
CATCH cx_sy_create_object_error INTO exc_ref.
exc_text = exc_ref->get_text( ).
MESSAGE exc_text TYPE 'I'.
ENDTRY.
ENDIF.

ENDMETHOD.

 

第五步,建立最終的呼叫程式,對於工廠類的方法GET_MATERIAL_OBJ,因為該方法為靜態方法,可以不建立類物件直接通過類名稱呼叫,呼叫後可以取得子類的物件例項。

然後再呼叫類的方法PRINT_INSP_DESC,根據多型,自動根據物料型別,實現子類具體的業務處理。

 

程式碼如下:

"示例程式6.3

REPORT zrep_cls_022.

DATA:
lv_exref TYPE REF TO cx_root,
lv_msgtxt TYPE string.
DATA go_material TYPE REF TO zcl_material.
"輸入物料號碼
PARAMETERS: p_matnr LIKE mara-matnr.
START-OF-SELECTION.
TRY.
"不用建立呼叫工廠類的例項,通過類名呼叫靜態方法
"GET_MATERIAL_OBJ,取得子類的物件例項
go_material = zcl_matl_spl_factory=>get_material_obj( p_matnr ).
IF go_material IS BOUND.
"呼叫類的方法PRINT_INSP_DESC ,實現子類具體的業務處理
go_material->print_insp_desc( ).
ELSE.
WRITE: / '對應物料型別未維護列印資訊.'.
ENDIF.
CATCH cx_root INTO lv_exref.
lv_msgtxt = lv_exref->get_text( ).
WRITE: / lv_msgtxt.
CLEANUP.
ENDTRY.

  

 

如圖6-22所示,執行程式,輸入物料號"FIN1001",其物料型別為"FERT

成品型別",程式碼如示例程式6.3所示。

 

 

 

 

如圖6-23所示,執行結果就是子類"ZCL_MATERIAL_FIN"成品物料類的列印方法。

 

 

 

 

如圖6-24所示,執行程式,輸入物料號"RAW2010",物料型別為"ROH原料型別",執行程式。

 

 

 

如圖6-25所示,執行結果就是子類"ZCL_MATERIAL_RAW"原料物料類的列印方法。

 

如圖6-26所示,執行程式,輸入物料號"SEMI001",物料型別為"HALB半成品型別",執行程式。

 

如圖6-27所示,執行結果就是未能找到子類,列印"對應物料型別未維護列印資訊."的資訊。

 

 

 

物料類的程式碼如示例程式6.4所示。

 

 

"示例程式6.4

為了節約篇幅,物料類的code based 程式碼請參照Github程式碼6.4

<https://github.com/ABAPOOP/ABAP_OOP_SAMPLE/blob/master/ABAP_OOP_Sample_6.04.txt >

  

看到上面的實現方法,大家可能會說"莫名其妙啊?例子中的程式碼原來一共就一個程式,20多行的程式碼,每次擴充套件我就複製黏貼,然後改一改就好了。

現在用了這些原則和模式,需要建立幾個類,又要建立表,並且全部程式碼變成了200多行,而其中近100行都是和列印無關的模式和控制的程式碼——這不是畫蛇添足,自找麻煩嗎?"

 

這是一個好問題,以下我們要講的所有原則和模式都是會引入一些"冗餘的"模式和控制程式碼,我們乾脆就在這一節裡闡述一下作者對為什麼採用這些原則和模式控制的理解。

 

為什麼要採用這些原則和模式呢?

  1. 為了建立一個良好的系統管理架構

如果你開了一家有一定的規模公司,你一定會架構好你的企業管理架構,管理學中的管理架構有管理層級和管理寬度的概念,層級就是從最高層到最底層的層級,管理寬度就是每層能夠有效監督的下屬人數。越是高層,對智力和抽象能力的要求越高,管理的方式就越是關注方向性和抽象性的問題,而不是具體化的業務。最高層一般就是3到13人(想想大多數公司董事會的人數,基本也就這麼多),作為董事長的你,主要管理好這幾個高層就可以了。而越往下的管理層級,管理的下屬人數就越多,他們關注的問題也就越為具體和瑣碎。越高層的人員替換和業務流程修改的代價越高(比如一個公司替換CEO的代價),而越低層級的人員替換和業務流程修改的代價卻越低。

 

一旦建立起來扁平而有效的管理架構和寬度,公司的業務發展就有了骨架,在管理層的運作下,基層員工的業務是很容易拓展的。從企業的人員"收益率"的角度出發,管理人員的收益率要遠大於具體的業務人員。

 

這些和具體業務無關的模式控制程式碼就像是公司的管理架構和管理人員,在業務簡單的時候,模式控制控制程式碼有100行,佔了50%的程式碼量,管理層當然低效而冗餘。

而現實中的業務從來都不是這麼簡單的,動輒以上千行計,這時,控制程式碼所佔程式的比率就不會超過10%了,管理效率就體現出來了。

而當代碼隨著業務迅速增長的時候,模式控制程式碼就像公司的管理層人員,不會像底層人員迅速增長,這時候的模式控制程式碼數量就在整個系統中的數量比重越來越小,但控制程式碼起到的作用依然是決定性的,系統的可擴充套件性,可維護性在一開始建立管理架構的時候就被決定了。

我們建立的模式控制程式碼(包括幾個類和資料表),就是搭建這個系統的管理架構,在程式很簡單的時候當然是小題大做,但處理現實中的複雜系統,搭建一個有前瞻性的架構,就是"畢先利其器"的必要過程.

 

 

  2.獲得更高的軟體的"程式碼收益率"

我們用一個新名詞,"程式碼收益率"來解釋。像計算像投資收益率一樣計算產出收益和投入的資產的比率。如下列公式所示,如果程式碼也需要計算收益率的話,我們計算一下模式與控制部分的程式碼收益率如何,列一個簡單的公式:

 

這裡,"手工編寫的業務程式碼行數"是隨著業務的擴充套件和改變而不斷增加的需要手工加入的新的邏輯(其他共有的邏輯則通過類的複製和繼承即可得到),而核心的"模式與控制程式碼"則增長不會很大,在良好的設計原則和模式下,甚至能做到這部分的零增長。

從成本的角度來看,開發成本在最開始的時候有架構師來定義最初的模式與控制,這時的成本是最高的。但系統擴充套件時,開發人員只要按照固定的模式新增新的類和配置,照貓畫虎即可,由一般的開發人員進行擴充套件就夠了,其開發成本是逐漸降低的。

由此可見,模式和控制程式碼是用於控制全域性的,所有新增的程式碼都能夠在它的控制之下,所以這種模式控制的程式碼的"程式碼收益率"是最高的。是值得你投入精力去學習和實現的。

 3. 降低軟體的開發和維護成本

舉一個實際專案中的例子,作者參與一個超過6000行的中型的ABAP後臺程式設計和測試,這個程式由基於BAdI的增強觸發,負責多種業務型別下的資料處理,一開始,我們用的是ABAP的一般結構化程式設計。隨著業務型別的增加(每幾個月都會增加一種型別),我們每次都要在已有的程式中新增新的業務型別,即便採用了功能和模組化設計,但修改的程式碼量還是重複和複雜,每次都要美國的架構師親自參與程式碼設計和指導,然後由印度和中國的ABAPer們開發實現。

測試就更加麻煩,每次的測試都要用一個Excel表格才能記錄下所有的回退功能測試(就是對以前的功能的測試,防止新加的功能影響以往的功能)。

還曾經因為一次程式碼更新了一個公有的模組,而回退測試沒有覆蓋全,造成了上線後的系統錯誤。

 

最後,我們採用了面向物件ABAP和設計原則(開放封閉原則)與模式(主要是增強的簡單工廠模式)重構了這個程式,建立了幾個控制類和控制資料表,將業務型別封裝到一個抽象類中,然後將每個具體的業務型別定義為這個抽象類的子類。控制類的程式碼和資料表如果用程式碼行數來核算的話只有200多行。

 

重構後,目前每次擴充套件業務時,只需要客戶定義好新業務的基本規則(這些規則間很類似,可以重用大部分程式碼),確定最核心的程式碼邏輯,然後交給一個一般的開發人員去做兩件事情:

先用SE24按照現有的子類為模板,複製出一個新的型別,修改其中的某些方法來實現新的規則(我們視這部分的實現類不是模式與控制程式碼,而是具體的業務實現程式碼)。

然後再到資料配置表SM30中新增上這種業務型別就可以了。即便是一般的開發人員,這個工作既不復雜也無風險。

而原來冗長的BAdI的主程式,目前只有100多行,並且自重構後,我們新增過多種業務型別,但這些模式控制程式碼和以前的業務類程式碼從來不用修改和增加過。

至於測試,就更高效了,重構後的程式碼,在前幾次新增規則時我們還做回退測試,因為每次新增業務型別的都是建立一個新的類(對擴充套件開放),從來都不會去修改以前的類和程式碼(對修改關閉),所以對以前的邏輯不會有任何影響,到後來我們只測試新加的業務功能,連繁瑣的回退測試都省略了。而且每次上線沒有因為這個新架構而產生任何問題。

架構師採用良好的架構設計後,對於後期的功能擴充套件來說,價格高的架構師基本再不用參與這類擴充套件工作,而價格低的開發人員也有章可循沒有開發風險,測試則更是可以自動化進行或者減少回退測試,進一步降低了成本。

 

表6-3對比了作者在實際工作中的參與的這一項程式碼重構前後的效果和成本對比。

 

NO.

專案

原有面向過程程式碼

重構後的面向物件程式碼

1

手寫程式碼行數

6000行,幾乎所有程式碼都需要手工編寫。

僅主程式程式碼行數就近2000行,閱讀和修改都比較困難。

3000行,(全部程式碼超過了一萬行,但是大部分程式碼都是系統生成的,或者採用複製類的方法自動生成的,並且程式碼分散在類方法中,易於理解和閱讀。新加功能時,我們複製一個新的類,然後只需要根據業務需要,修改類中的幾個方法即可,而控制類的程式碼基本沒有增長過。)

2

重新新增一個業務功能後,回退測試(Regression Test)所耗時間

30小時,每次新增一個新業務功能,都要修改現有程式碼,必須進行全面的回退測試,保證新加功能後的程式碼沒有影響以往的業務邏輯。

0小時,因為採用了增強的工廠模式,遵循了開放封閉原則,以前的業務功能程式碼不用進行任何修改,以前的程式碼連編譯都不需要,不再需要回退測試。

3

新功能測試所需時間

5小時

5小時,開放封閉原則允許新加功能,新功能測試和原來用時一致。

4

上線後發生的錯誤次數

10+次,因為每都需要對原有程式碼進行頻繁調整,出錯概率很大。

1次,並且是新功能遷移時發生的問題,因為增新功能不會影響以往的業務邏輯

5

重新新增一個業務功能所需時間

80+小時,每次都需要架構師親自審閱和設計,指導開發程式碼。

30+小時,僅需架構師設計和審閱程式碼即可。

 

為了驗證我的解釋,我們來測試一下,當業務上有一個新的型別"半成品物料"需要新增進來時,看該架構是否能做到只新增新程式碼,不用修改原來的程式碼。

 

如圖6-28,我們新加"半成品物料"後的系統結構如下所示,需新加一個子類B3,然後新加一條資料庫記錄即可,對虛線內的原有的系統程式碼(也包括報表主程式ZREP_CLS_022)不用做任何修改,連重新編譯都不需要。

 

 

 

我們加入一個新的物料類"ZCL_MATERIAL_SEMI",代表物料型別 "半成品型別",專門處理SAP物料型別為"HALB"的半成品物料的列印。

 

如圖6-29所示,首先進入SE24,根據現有的類ZCL_MATERIAL_RAW,點選複製按鈕,複製一個近似的半成品類ZCL_MATERIAL_SEMI。

 

 

如圖6-30所示,複製一個半成品類ZCL_MATERIAL_SEMI。

 

 

如圖6-31所示,半成品類ZCL_MATERIAL_SEMI同樣是繼承自類ZCL_MATERIAL,僅需要修改對應的Description等資訊。

 

 

 

如圖6-32所示,為半成品類ZCL_MATERIAL_SEMI重新編寫那些需要修改的方法的程式碼邏輯,不用修改的程式碼的方法則繼續沿用以前的邏輯,不用處理。

 

 

 

如圖6-33所示,設定半成品物料的列印資訊,然後啟用這個半成品類。

 

如圖6-34所示,新增完新的類後,第二步就是進入SM30,為類登錄檔ZINSP_DESC_T,新新增記錄,SAP標準物料型別為HALB(半成品),對應的型別處理類ZCL_MATERIAL_SEMI。

 

 

 

如圖6-35所示,前兩步的新增程式碼和新增記錄完成後,我們不用修改,也不用編譯任何以前的類和主程式程式碼,直接執行程式ZREP_CLS_022,然後輸入物料型別SEMI001,物料其對應的SAP標準物料型別為HALB(半成品)。

 

 

 

如圖6-36所示,程式碼可以根據物料型別,找到相應的處理類,觸發相應的半成品物料類的方法來處理新的業務邏輯,而且,程式原來的針對原料和成品的處理邏輯不受任何影響。可見,該架構符合對擴充套件開放,對修改關閉的開放封閉原則,擁有較為穩定和易於擴充套件的系統結構。

 

 

 

該架構的核心就是利用了多型的特性,將"物料"抽象為較為穩定的,很少修改的抽象的父類;而具體的物料(成品,半成品,原料)則代表了具體多變的可以隨時新增和修改的具體的物料型別。

採用繼承讓各個物料子類有不同的處理方式。

採用多型根據當前的物料型別建立對應的處理類物件進行處理。

 

該系統邏輯依然是根據不同的物料型別來處理物料的不同檢驗方式,但把原來固定的IF / ELSE邏輯改成了工廠類中動態的表記錄的讀取的方式,獲得相應物料型別的處理類的名稱,並動態建立上轉型多型的父類物件。從而完全符合了開放封閉原則。該"增強的簡單工廠"架構是一種很實用並符合很多業務場景(不僅僅SAP業務開發會遇到的場景)的設計方式。

 

以上的開放封閉原則的示例即能幫助我們來實現一種即插即用的元件式的較為穩定和易於擴充套件的軟體系統架構。

 

 

 

《SAP ABAP面向物件程式設計:原則、模式及實踐》

 

https://book.douban.com/subject/30317853/

http://www.duokan.com/shop/tbt/book/179473

https://item.jd.com/12423999.html

https://e.jd.com/30429611.html

 

6.3 開放封閉原則(OCP)

開閉原則(Open-Closed Principle, OCP)指的是,一個類或者模組,如果在業務修改或者功能需要擴充套件時,應儘可能保證只通過新新增程式碼,而不是修改原有程式碼的情況下完成。

 

該原則是Eiffel 語言的設計者,法國電腦科學家Meyer提出的,最開始的描述是:當向模組新增欄位或方法時,不可避免地需要對呼叫這個模組的程式進行修改,解決這問題的方法是採用依賴於面向物件繼承(特別是從實際父類進行的實現繼承)的方法,而不是直接修改原來的程式。

從20世紀90年代開始,OCP的原則依然被大家遵循,只是解決的方式變了。隨著抽象類和介面等方式的大量應用,開放封閉原則開始提倡採用抽象類或介面的方法,而不是實現繼承來解決問題。

解決方式是現存的介面或抽象類對於修改原有的方法和屬性是封閉的,但對新新增的方法和屬性則是開放和允許的。

 

為什麼要對原有的程式碼進行保護,對新加的程式碼開放呢?

所有的軟體和程式都有一個生命週期,當需求和業務發生擴充套件和更新時,需要更新軟體(修復缺陷和軟體重構時的更新除外),要儘可能保證軟體的基本框架不變,儘可能不修改現有的程式碼,而是新增新的實現程式碼,使得軟體具有好的穩定性和可維護性。

 

實現擴充套件而不修改原有程式碼的基礎就是採用介面或者抽象類等機制完成的。經過良好的定義,系統可以擁有一個相對穩定的抽象層,將業務行為下沉到具體的實現層中。

如果業務有擴充套件,可以將擴充套件的業務放到實現層中,只新增新的程式碼,不修改已有的程式碼,通過抽象層,將新的業務邏輯指定到新新增的業務實現層中。

如果業務有修改,無須對抽象層進行任何改動,新增實現程式碼,替換固定的實現層程式碼,而不會影響實現層其他模組的行為。

 

設計例項:

一個公司中有多種物料型別,每種物料都要根據物料型別列印不同的檢驗說明單據,這個檢驗說明是由一個非面向物件的程式來控制的,當公司中的物料型別增加一種時,IT部門就被要求為這個程式新增一種列印方法。IT部門每次修改這個列印程式後,都要為以前的每一種的列印都做一個回退測試,保證新加的程式碼沒有影響以往的業務。

如圖6-4所示,程式流程圖如下,當新增一種新物料型別時,程式就又多一組判斷:

 

 

 

 

程式碼如示例程式6.1所示。

"示例程式6.1

REPORT zrep_cls_021.

DATA gv_material_type TYPE mara-mtart.
DATA gv_material TYPE mara-matnr.

PARAMETERS: p_matnr LIKE mara-matnr.

START-OF-SELECTION.

SELECT SINGLE mtart FROM mara INTO gv_material_type
WHERE matnr = p_matnr.

IF sy-subrc = 0.

"型別判斷
IF ( gv_material_type EQ 'ROH') .
WRITE: / '此物料為原料,需檢查採購合同 '.
ELSEIF ( gv_material_type EQ 'FERT') .
WRITE: / '此物料為成品,需檢查銷售合同 '.
ENDIF.
ENDIF.

  

當每次公司出現一個新的物料型別時,就要新增一個針對這個新型別的相關檢驗的要求,我們就需要去修改這個IF ELSE程式碼新增一個新的邏輯,主程式就要每一次都進行修改,這就不符合開放封閉原則。

 

對於不斷變化的業務,如果我們每次修改原有的程式碼,除了要對增加的功能進行測試,對原有的功能還必須要做回退測試(Regression Test),保證新的邏輯沒有影響以前的業務邏輯。但如果軟體能夠做到僅增加新的程式碼,不修改,或基於一定的規則的修改原有程式碼,這樣就能保證工作量和程式碼出錯的機率大幅下降。這就是"開放-關閉"原則,即軟體擴充套件時只新增新程式碼而不是修改原有程式碼。

 

我們用面向物件的程式設計,並採用一個"改進的簡單工廠" 設計模式(也就是基於配置表的簡單工廠模式,具體結構見第7章第3節)來對系統進行重構,說明一下如何做到重構中的開放封閉原則。(例子中的這個程式因為太簡單,其實是沒必要進行面向物件重構的,但這個例子是足夠能說明實際業務中的重構過程和如何做到的遵循開放封閉原則。重構後,程式碼量會增加,但卻是十分值得的。)

 

如圖6-5所示,首先將業務抽象為三層,第一層為物料物件生成工廠類,相當於主程式中的操作者,用於建立物料類的物件例項。第二層為抽象的物料類,由物料物件生成工廠類直接呼叫。第三層是具體的物料類,代表具體的物料型別,可以不斷從抽象類中繼承和新增新的子類,具體的解釋如表6-2所示。

 

其中第二層抽象類隔離開了操作類和具體物料類,使得主程式可以不必修改,就可以處理新新增的業務邏輯。

 

 

程式和實體列表

解釋

第一層類:物料物件生成工廠類

一個類,用於建立(生產)物料類的物件例項。

第二層抽象類:抽象的物料類

一個類,抽象的物料類。

第三層類:具體的物料類

可以有多個類,每個代表一個具體的物料型別和相應的處理方法。

資料表:物料型別和相應具體物理類的表

表中記錄了SAP物料的型別和對應具體類的類名。

 

 

 

我們逐步介紹如何建立這些類和實體:

第一步,如圖6-6所示,建立抽象的物料類ZCL_MATERIAL,這是第二層的抽象類,將多樣的具體業務抽象為一種模式。設定類的型別為"Abstract"抽象類。

 

如圖6-7所示,建立一個屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼(MARA-MATNR)。

 

 

如圖6-8所示,設定方法PRINT_INSP_DESC,可見型別為Public,無引數。不必定義具體邏輯,該方法表明了物料類擁有列印檢驗說明的能力,但具體實現程式碼是由其子類各自實現的。

 

 

 

第二步,如圖6-9所示,建立第三層的抽象的子類。分別為"成品物料類"ZCL_MATERIAL_FIN,"原料物料類"ZCL_MATERIAL_RAW,這一層是最底層的實現類,代表了具體的實現方法,如果有新的業務新增時,也是在這一層次建立新的類。

 

建立成品物料類ZCL_MATERIAL_FIN,從抽象物料類繼承。

 

 

如圖6-10所示,子類自動繼承屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼。MARA-MATNR。

 

 

如圖6-11所示,重定義繼承來的方法PRINT_INSP_DESC。

 

如圖6-12所示,該方法是對應的成品物料類的具體操作方法,比如獲取固定資料,生成固定格式,併發送到相應的輸出印表機等。我們這裡就簡單的寫成"此物料為成品,需檢查銷售合同",用於代表現實中複雜的邏輯。

 

 

如圖6-13所示,建立原料物料類ZCL_MATERIAL_RAW,從抽象物料類繼承。

 

 

如圖6-14所示,自動繼承屬性MV_MATERIAL,為受保護可見度, 型別為物料號碼。MARA-MATNR。

 

 

如圖6-15所示,重定義繼承來的方法PRINT_INSP_DESC。

 

 

如圖6-16所示,該方法是對應的原料物料類的具體操作方法,比如獲取採購或生產資料,生成固定格式的報表,併發送到相應的輸出印表機等。我們寫成"此物料為原料,需檢查採購合同",用於代表現實中複雜的邏輯。

 

 

第三步,如圖6-17所示,建立表ZINSP_DESC_T:

列SORT_NUM代表序號。

列MATERIAL_TYPE儲存著SAP標準的物料型別。

列CLASS_TYPE儲存著處理相應物料型別的相應的物料類類名(類名需要大寫)。然後參照"資料字典"章節,維護列的Domain,資料型別和維護表等工作。

 

 

如圖6-18所示,執行SM30,將SAP標準的物料型別"ROH原料型別",及其對應的處理類"ZCL_MATERIAL_RAW",物料型別"FERT成品型別",極其對應的處理類"ZCL_MATERIAL_FIN"。新增到表中。

在Java和.NET中,大多使用XML檔案來儲存類似的服務註冊,SAP ABAP中最為方便的就是用資料庫表。

 

 

第四步,如圖6-19所示,建立簡單工廠類ZCL_MATL_SPL_FACTORY,這是第一層的呼叫類,模式控制的核心程式碼定義在這一層 ,這個類將實現根據物料動態建立物料子類型別(也就是用於建立物料類物件的簡單類工廠),並實現多型的能力,多型在這個架構中將展現其威力。

 

類ZCL_MATL_SPL_FACTORY是普通的Public型別的非抽象類。

 

如圖6-20所示,設定方法GET_MATERIAL_OBJ,可見型別為Public,該方法是根據物料號碼,獲取物料型別,繼而獲得對應的物料類的名稱並建立類物件例項,方法可以是靜態型別方法(Static Method),即可以用類名來訪問方法(用=>符呼叫)。

 

如圖6-21所示,設定方法的輸入引數 IV_MATERIAL_ID,是傳入的物料號碼MARA-MATNR。

設定方法的返回引數 RO_MATERIAL,型別是抽象類物料物件型別ZCL_MATERIAL。方法將其例項化後作為返回值傳出,例項化的物件都是物料類的可例項化的子類物件,然後賦值給父類物料類物件進行上轉型,此處就是典型的多型的應用。

 

程式碼根據傳入的物料號碼,在標準表MARA中查詢出物料型別,然後根據物料型別去自定義表格中查詢對應的處理類,然後建立對應類物件例項作為返回的引數,程式碼如示例程式6.2所示,程式碼中的"動態建立類物件 ro_material,型別為登錄檔中的類的型別"即是按子類型別建立父類物件,即向上轉型的多型的實現。

 

"示例程式6.2

METHOD get_material_obj.

DATA: lv_material_type TYPE mara-mtart,
lv_material TYPE mara-matnr,
lv_class TYPE char30.

DATA:
exc_ref TYPE REF TO cx_root,
exc_text TYPE string,
lv_type_name TYPE string,
lv_method_name TYPE string.

"先根據物料號碼查詢物料型別
SELECT SINGLE mtart FROM mara INTO lv_material_type
WHERE matnr = iv_material_id.
IF sy-subrc = 0.
"在根據物料型別查詢登錄檔中對應的物料類的型別
SELECT SINGLE class_type FROM zinsp_desc_t INTO lv_class
WHERE material_type = lv_material_type.
ENDIF.
"如果有查詢記錄則開始動態建立類
IF sy-subrc = 0.
lv_type_name = lv_class. "類名必須大寫
TRANSLATE lv_type_name TO UPPER CASE.
TRY.
"動態建立類物件 ro_material,型別為登錄檔中的類的型別,
CREATE OBJECT ro_material TYPE (lv_type_name).
CATCH cx_sy_create_object_error INTO exc_ref.
exc_text = exc_ref->get_text( ).
MESSAGE exc_text TYPE 'I'.
ENDTRY.
ENDIF.

ENDMETHOD.

 

第五步,建立最終的呼叫程式,對於工廠類的方法GET_MATERIAL_OBJ,因為該方法為靜態方法,可以不建立類物件直接通過類名稱呼叫,呼叫後可以取得子類的物件例項。

然後再呼叫類的方法PRINT_INSP_DESC,根據多型,自動根據物料型別,實現子類具體的業務處理。

 

程式碼如下:

"示例程式6.3

REPORT zrep_cls_022.

DATA:
lv_exref TYPE REF TO cx_root,
lv_msgtxt TYPE string.
DATA go_material TYPE REF TO zcl_material.
"輸入物料號碼
PARAMETERS: p_matnr LIKE mara-matnr.
START-OF-SELECTION.
TRY.
"不用建立呼叫工廠類的例項,通過類名呼叫靜態方法
"GET_MATERIAL_OBJ,取得子類的物件例項
go_material = zcl_matl_spl_factory=>get_material_obj( p_matnr ).
IF go_material IS BOUND.
"呼叫類的方法PRINT_INSP_DESC ,實現子類具體的業務處理
go_material->print_insp_desc( ).
ELSE.
WRITE: / '對應物料型別未維護列印資訊.'.
ENDIF.
CATCH cx_root INTO lv_exref.
lv_msgtxt = lv_exref->get_text( ).
WRITE: / lv_msgtxt.
CLEANUP.
ENDTRY.

  

 

如圖6-22所示,執行程式,輸入物料號"FIN1001",其物料型別為"FERT

成品型別",程式碼如示例程式6.3所示。

 

 

 

 

如圖6-23所示,執行結果就是子類"ZCL_MATERIAL_FIN"成品物料類的列印方法。

 

 

 

 

如圖6-24所示,執行程式,輸入物料號"RAW2010",物料型別為"ROH原料型別",執行程式。

 

 

 

如圖6-25所示,執行結果就是子類"ZCL_MATERIAL_RAW"原料物料類的列印方法。

 

如圖6-26所示,執行程式,輸入物料號"SEMI001",物料型別為"HALB半成品型別",執行程式。

 

相關推薦

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-6 迪米特原則最少知道原則

/** * 軟體設計七大原則-迪米特原則 學習筆記 * @author cnRicky * @date 2018.11.10 */ 迪米特原則(最少知道原則) 一個物件應該對其他物件保持最少的瞭解。又叫最少知道原則 迪米特原則主要強調:儘量降低類與類之間的耦合 優點:降低類與類之

軟件架構設計學習總結23軟件架構設計的6原則

str 軟件架構 edge 程序員 難點 posit not 幫我 mman 1. 單一職責原則(Single Responsibility Principle - SRP) 原文:There should never be more than one reason fo

實踐作業3白盒測試實踐小組作業記錄3

自己 logs 軟件學院 idt str strong span mil tro 會議時間:2017.12.21 會議地點:軟件學院北樓507 參會人員:魯慧敏、寧莉莎、張江、王瑞、李佳明 會議目的:將大家討論後回去自己完成版塊的單元測試和缺陷報告,靜態代碼評估遇到的問題拿

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-1 本章導航

/** * 軟體設計七大原則-本章導航 學習筆記 * @author cnRicky * @date 2018.11.7 */ 本章導航 開閉原則(所有原則的一個基礎) 依賴倒置原則 單一職責原則 介面隔離原則 迪米特法則(最少知道原則) 里氏替換原則 合成/複用原則(組合

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-2 開閉原則

/** * 軟體設計七大原則-開閉原則 * @author cnRicky * @date 2018.11.7 */ 開閉原則 定義:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉 強調的是用抽象構建框架,用實現擴充套件細節 優點:提高軟體系統的可複用性及可維護性 開閉原則

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-5 介面隔離原則

/** * 軟體設計七大原則-介面隔離原則 學習筆記 * @author cnRicky * @date 2018.11.10 */ 介面隔離原則 定義:用多個專門的介面,而不使用單一的總介面,客戶端不應該依賴它不需要的介面 一個類對一個類的依賴應該建立在最小的介面上 建立單一介

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-4 單一職責原則

/** * 軟體設計七大原則-單一職責原則 學習筆記 * @author cnRicky * @date 2018.11.10 */ 單一職責原則 定義:不要存在多於一個導致類變更的原因 一個類只負責一個職責,如果分別有兩個職責,那就建立兩個類分別負責職責1和職責2 一個類/介面/方法只負

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-3 依賴倒置原則

/** * 軟體設計七大原則-依賴倒置原則 學習筆記 * @author cnRicky * @date 2018.11.10 */ 依賴倒置原則 高層模組不應該依賴低層模組,二者都應該依賴其抽象 抽象不應該依賴細節;細節應該依賴抽象 針對介面程式設計,不要針對實現程式設計(儘

快樂程式設計大本營【java語言訓練班】 6課:用java的物件和類程式設計

快樂程式設計大本營【java語言訓練班】 6課:用java的物件和類程式設計 第1節. 什麼是物件和類 第2節. 物件的屬性和方法 第3節. 類的繼承 第4節. 使用舉例:建立類,定義方法,定義屬性 第5節. 使用舉例:建立物件,屬性賦值與使用,方法呼叫; 第6節. 使用舉例:類繼承及物件使用 地址如下

藍的成長記——追逐DBA1):奔波於路上,挺進山東 藍的成長記——追逐DBA(3):古董上操作,資料匯入匯出成了問題 藍的成長記——追逐DBA8):重拾SP報告,回憶oracle的STATSPACK實驗 藍的成長記— —追逐DBA9):國慶漸去,追逐DBA,新規劃,新啟程

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

大話設計模式C++實現-第3.4.5-設計原則1

第三章-單一職責原則 (1).就一個類而言,應該僅有一個引起它變化的原因。 (2)如果一個類承擔的職責過多,就等於把這些職責耦合在了一起,一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。這種耦合會導致脆弱的設計,當發生變化時,設計會遭受到意想不到的破壞。 (3)軟體設計真正要做

[SLAM]番外篇一起做RGB-D SLAM(6)

    本文轉自高翔老師的部落格,建議在學完教程的第二講後,插入學習,做到工程快速入門。     原文連結:https://www.cnblogs.com/gaoxiang12/p/4739934.html 圖優化工具g2o的入門   在上一講

用python寫完成一個員工管理系統 要求儲存員工的工號姓名年齡性別工資 1員工錄入 2查詢員工資訊 3修改員工資訊 4刪除 5根據工號檢視 6退出

   完成一個員工管理系統    要求儲存員工的工號、姓名、年齡、性別、工資    1、員工錄入    2、查詢員工資訊    3、修改員工資訊    4、刪除  &nb

4【Python】Python 3入門(模組/面向物件/錯誤和異常/檔案操作/序列化/命名規範)

一、模組     編寫模組有很多種方法,其中最簡單的一種便是建立一個包含函式與變數、以 .py 為字尾的檔案。     另一種方法是使用撰寫 Python 直譯器本身的本地語言來編寫模組。舉例來說,你可以使用 C 語言來撰寫 Python 模組,並且在編譯後,你可以通過標準 Pyth

【小練習】程式設計基本概念賦值語句_常用運算子3

1.練習程式碼 #include "stdafx.h" #include <iostream> using namespace std; int _tmain(int argc, _TC

灰度釋出灰度很簡單,釋出很複雜&灰度釋出灰度法則6點認識

什麼是灰度釋出,其要點有哪些?   最近跟幾個聊的來的同行來了一次說聚就聚的晚餐,聊了一下最近的工作情況如何以及未來規劃等等,酒足飯飽後我們聊了一個話題“灰度釋出”。 因為筆者所負責的產品還沒有達到他們產品使用者的量級上(最低的都在1千萬+),也就談不上灰度釋出這一環節,所以只有聽的

java程式設計從陣列(1,2,4,6,9)中列出所有相加等於20的組合,例如9+9+2=20

原創 public class Test1 { public static void main(String[] args) { int[] nums = {1,2

開發日常小結9如何在專案中定位列舉類的作用,並使用列舉類?-- 列舉類描述業務物件狀態的例項詳解

2018年4月11日列舉類在專案中,作為常量狀態經常被使用,剛剛開始接觸時還有點不適應,現在已經能夠靈活的使用了。【1】概念  先看一個簡單的列舉類。package enumcase; public enum SeasonEnum { SPRING,SUMMER,F

《Unix網路程式設計》卷1套接字聯網API(第3版)簡介傳輸層套接字程式設計

全書共31章+附錄。 計劃安排:吃透這本書,一天三章+原始碼,並實測程式碼做當天筆記,CSDN見。時間安排:計劃時間1.5個月 == 6個週末 == 12天。 2017.08.05    第01-03章:TCP/IP簡介、傳輸層、套接字程式設計簡介2017.08.06  

aop面向切面程式設計原理,這個比喻我服辣,6翻了

面向切面程式設計(AOP是Aspect Oriented Program的首字母縮寫) ,我們知道,面向物件的特點是繼承、多型和封裝。而封裝就要求將功能分散到不同的物件中去,這在軟體設計中往往稱為職責分配。實際上也就是說,讓不同的類設計不同的方法。這樣程式碼就分散到一個個的類中去了。這樣做的好處是降低了程