開閉原則:設計一個模塊的時候,應當使這個模塊可以在不修改原有代碼的前提下被擴展。
這個原則是保證系統具有擴展性的基本原則。我理解有幾個要點:1、要能夠復用;2、擴展時只增加新方法、新類;3、不得不修改代碼時,修改的范圍必須是局部的、隱藏的;
通常變更有三種方式,一種是橫向變更,例如售票系統,原來只能售火車票,現在要可以售機票;第二種是縱向變更,例如在某個流程裡插入新活動或跳過活動;第三種是局部修改,就是原有功能的業務規則發生了變化。對於前兩種變更比較容易處理,只要在設計時注意抽象,通過接口、繼承、override或event即可擴充。對於第三種變更,估計要修改代碼了。雖然可以這樣分類,但實際上以上三種變更通常是同時發生的、相互交織的。
以庫存管理業務單據為例,有出庫單、入庫單、移庫單等。通常單據結構都很相似,包含頭表,行表,但個別字段有差異。新增一個單據,先在頭表插入一條記錄,然後在行表插入若干記錄。更新單據時,先更新頭表記錄,然後清空詞單據在行表裡的原有記錄,再插入新的行表記錄,刪除單據的過程也非常相似。此外,在單據增刪改時要記錄日志,在單據提交時,還要修改庫存。所有這些操作十分相似,可以抽象出來。
現在要設計一個單據處理通用業務類,負責單據暫存、修改、提交、刪除幾個基本業務,以及相關的日志和庫存操作。
1、分析
單據操作: Save()/Submit()/Delete()/Log()/ChangeStock()
相關數據: 頭表實體、行表實體集;與日志有關的一些屬性;與庫存有關的一些屬性;插入頭表時,要有一個接口獲取單據編號;因為插入行表時外鍵值需要用到頭表記錄的主鍵值,所以需要有個接口獲取頭表主鍵值,並有一個接口給行表實體的外鍵賦值;
可能的變更: 1、將來可能增加審批功能,單據提交後審判通過才改變庫存;2、增加新的單據類型;3、將來增加訂單管理,那麼庫存操作除了出入庫、還會增加在途轉入庫存、庫存轉出在途等功能。
2、設計
方案1、抽象類+子類,只關注操作,不關注數據;
單據處理類 BillProcess (抽象類):
公有虛方法 Save()/Submit()/Delete()
私有虛方法 Log()/ChangeStock()
擴展:增加審批功能,只需在基類增加新的虛方法Audit,子類實現新的虛方法; 增加單據類型時,只需實現新的單據,增加在途功能,只需修改 ChangeStock;
評價:此方案雖然很容易擴展,除了需要對ChangeStock作修改外,基本符合開閉原則;但過於抽象,子類需要實現全部操作,基類僅僅起到規范方法名稱的作用;此方案的復用度太低;
方案2、抽象類實現部分模板方法,所有方法都沒有參數,抽象類沒有任何字段,全部給子類實現;
單據處理類 BillProcess (抽象類):
公有虛方法 Save(): 調用IsNew()判斷是新增還是更新,如果是新增,設置單據編號SetBillNumber,插入頭表InsertHeader,設置行表外鍵SetBillLineHeaderID;若是更新,則更新頭表UpdateHeader,刪除行表DeleteLines(),設置行表外鍵SetBillLineHeaderID,插入行表InsertLines(); 調用 Log();
公有虛方法 Submit(): 調用 Save(),調用 ChangeStock(); 調用 Log();
公有虛方法 Delete(): 調用 DeleteHeader(),調用 DeleteLines(); 調用 Log();
保護虛方法 IsNew,判斷是新增單據還是更新單據;
保護虛方法 SetBillNumber