開放封閉原則(OCP,Open Closed Principle)

開放封閉原則(OCP,Open Closed Principle)是所有面向對象原則的核心。軟件設計本身所追求的目標就是封裝變化、降低耦合,而開放封閉原則正是對這一目標的最直接體現。其他的設計原則,很多時候是爲實現這一目標服務的,例如以Liskov替換原則實現最佳的、正確的繼承層次,就能保證不會違反開放封閉原則。  關於開放封閉原則,其核心的思想是:  軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。  因此,開放封閉原則主要體現在兩個方面:  對擴展開放,意味着有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。  對修改封閉,意味着類一旦設計完成,就可以獨立完成其工作,而不要對類進行任何修改。  “需求總是變化”、“世界上沒有一個軟件是不變的”,這些言論是對軟件需求最經典的表白。從中透射出一個關鍵的意思就是,對於軟件設計者來說,必須在不需要對原有的系統進行修改的情況下,實現靈活的系統擴展。而如何能做到這一點呢?  只有依賴於抽象。實現開放封閉的核心思想就是對抽象編程,而不對具體編程,因爲抽象相對穩定。讓類依賴於固定的抽象,所以對修改就是封閉的;而通過面向對象的繼承和對多態機制,可以實現對抽象體的繼承,通過覆寫其方法來改變固有行爲,實現新的擴展方法,所以對於擴展就是開放的。這是實施開放封閉原則的基本思路,同時這種機制是建立在兩個基本的設計原則的基礎上,這就是Liskov替換原則和合成/聚合複用原則。關於這兩個原則,我們在本書的其他部分都有相應的論述,在應用反思部分將有深入的討論。  對於違反這一原則的類,必須進行重構來改善,常用於實現的設計模式主要有Template Method模式和Strategy模式。而封裝變化,是實現這一原則的重要手段,將經常發生變化的狀態封裝爲一個類。  應用反思  站在銀行窗口焦急等待的用戶,在長長的隊伍面前顯得無奈。所以,將這種無奈遷怒到銀行的頭上是理所當然的,因爲銀行業務的管理顯然有不當之處。銀行的業務人員面對蜂擁而至的客戶需求,在排隊等待的人們並非只有一種需求,有人存款、有人轉賬,也有人申購基金,繁忙的業務員來回在不同的需求中穿梭,手忙腳亂的尋找各種處理單據,電腦系統的功能模塊也在不同的需求要求下來回切換,這就是一個發生在銀行窗口內外的無奈場景。而我每次面對統一排隊的叫號系統時,都爲前面長長的等待人羣而叫苦,從梳理銀行業務員的職責來看,在管理上他們負責的業務過於繁多,將其對應爲軟件設計來實現,你可以將這種拙劣的設計表示如圖1所示。  按照上述設計的思路,銀行業務員要處理的工作,是以這種方式被實現的:  class BusyBankStaff   {   private BankProcess bankProc = new BankProcess();   // 定義銀行員工的業務操作  public void HandleProcess(Client client)   {   switch (client.ClientType)   {   case "存款用戶":   bankProc.Deposit();   break;   case "轉賬用戶":   bankProc.Transfer();   break;   case "取款戶":   bankProc.DrawMoney();   break;   }   }   }   這種設計和實際中的銀行業務及其相似,每個BusyBankStaff(“繁忙的”業務員)接受不同的客戶要求,一陣手忙腳亂的選擇處理不同的操作流程,就像示例代碼中的實現的Switch規則,這種被動式的選擇造成了大量的時間浪費,而且容易在不同的流程中發生錯誤。同時,更爲嚴重的是,再有新的業務增加時,你必須修改BankProcess中的業務方法,同時修改Switch增加新的業務,這種方式顯然破壞了原有的格局,以設計原則的術語來說就是:對修改是開放的。 以這種設計來應對不斷變化的銀行業務,工作人員只能變成BusyBankStaff了。分析這種僵化的代碼,至少有以下幾點值得關注:銀行業務封裝在一個類中,違反單一職責原則;有新的業務需求發生時,必須通過修改現有代碼來實現,違反了開放封閉原則。  解決上述麻煩的唯一辦法是應用開放封閉原則:對擴展開放,對修改封閉。我們回到銀行業務上看:爲什麼這些業務不能做以適應的調整呢?每個業務員不必周旋在各種業務選項中,將存款、取款、轉賬、外匯等不同的業務分窗口進行,每個業務員快樂地專注於一件或幾件相關業務,就會輕鬆許多。綜合應用單一職責原則來梳理銀行業務處理流程,將職責進行有效的分離;而這樣仍然沒有解決業務自動處理的問題,你還是可以聞到僵化的壞味道在系統中瀰漫。  應用開發封閉原則,可以給我們更多的收穫,首先將銀行系統中最可能擴展的部分隔離出來,形成統一的接口處理,在銀行系統中最可能擴展的因素就是業務功能的增加或變更。對於業務流程應該將其作爲可擴展的部分來實現,當有新的功能增加時,不需重新梳理已經形成的業務接口,然後再整個系統要進行大的處理動作,那麼怎麼才能更好的實現耦合度和靈活性兼有的雙重機制呢?  答案就是抽象。將業務功能抽象爲接口,當業務員依賴於固定的抽象時,對於修改就是封閉的;而通過繼承和多態機制,從抽象體派生出新的擴展實現,就是對擴展的開放。  依據開放封閉原則,進行重構,新的設計思路如圖2所示。  圖2 面向抽象的設計  按照上述設計實現,用細節表示爲:  interface IBankProcess   {   void Process();   }   然後在隔離的接口上,對功能進行擴展,例如改造單一職責的示例將有如下的實現:  // 按銀行按業務進行分類  class DepositProcess : IBankProcess   {   //IBankProcess Members   #region IBankProcess Members   public void Process()   {   // 辦理存款業務  throw new Exception("The method or operation is not implemented.");   }   #endregion   }   class TransferProcess : IBankProcess   {   //IBankProcess Members   #region IBankProcess Members   public void Process()   {   // 辦理轉賬業務  throw new Exception("The method or operation is not implemented.");   }   #endregion   }   class DrawMoneyProcess : IBankProcess   {   //IBankProcess Members   #region IBankProcess Members   public void Process()   {   // 辦理取款業務  throw new Exception("The method or operation is not implemented.");   }   #endregion   }   這種思路的轉換,會讓複雜的問題變得簡單明瞭,使系統各負其責,人人實惠。有了上述的重構,銀行工作人員徹底變成一個EasyBankStaff(“輕鬆”的組織者):  class EasyBankStaff   {   private IBankProcess bankProc = null;   public void HandleProcess(Client client)   {   bankProc = client.CreateProcess();   bankProc.Process();   }   }   銀行業務可以像這樣被自動地實現了:  class BankProcess   {   public static void Main()   {   EasyBankStaff bankStaff = new EasyBankStaff();   bankStaff.HandleProcess(new Client("轉賬用戶"));   }   }   你看,現在一切都變得輕鬆自在,匆忙中辦理業務的人們不會在長長的隊伍面前一籌莫展,而業務員也從繁瑣複雜的勞動中解脫出來。當有新的業務增加時,銀行經理不必爲重新組織業務流程而擔憂,你只需爲新增的業務實現IBankProcess接口,系統的其他部分將絲毫不受影響,辦理新業務的客戶會很容易找到受理新增業務的窗口,如圖5所示。  圖5符合OCP的設計  對應的實現爲:  class FundProcess : IBankProcess   {   //IBankProcess Members   #region IBankProcess Members   public void Process()   {   // 辦理基金業務  throw new Exception("The method or operation is not implemented.");   }   #endregion   }   可見,新的設計遵守了開放封閉原則,在需求增加時只需要向系統中加入新的功能實現類,而原有的一切保持封閉不變的狀態,這就是基於抽象機制而實現的開放封閉式設計。  然而,細心觀察上述實現你會發現一個非常致命的問題:人們是如何找到其想要處理的業務窗口,難道銀行還得需要一部分人力來進行疏導?然而確實如此,至少當前的設計必須如此,所以上述實現並非真正的業務處理面貌,實際上當前“輕鬆”的銀行業務員,還並非真正的“輕鬆”,我們忽略了這個業務系統中最重要的一部分,就是用戶。當前,用戶的定義被實現爲:  class Client   {   private string ClientType;   public Client(string clientType)   {   ClientType = clientType;   }   public IBankProcess CreateProcess()   {   switch (ClientType)   {   case "存款用戶":   return new DepositProcess();   break;   case "轉賬用戶":   return new TransferProcess();   break;   case "取款用戶":   return new DrawMoneyProcess();   break;   }   return null;   }   }   如果出現新增加的業務,你還必須在長長的分支語句中加入新的處理選項,switch的壞味道依然讓每個人看起來都倒胃口,銀行業務還是以犧牲客戶的選擇爲代價,難道不能提供一個自發組織客戶尋找業務窗口的機制嗎?  其中的設計原則就是用於解決上述問題的。  規則建議  l 開放封閉原則,是最爲重要的設計原則,Liskov替換原則和合成/聚合複用原則爲開放封閉原則的實現提供保證。  l 可以通過Template Method模式和Strategy模式進行重構,實現對修改封閉、對擴展開放的設計思路。  l 封裝變化,是實現開放封閉原則的重要手段,對於經常發生變化的狀態一般將其封裝爲一個抽象,例如銀行業務中的IBankProcess接口。  l 拒絕濫用抽象,只將經常變化的部分進行抽象,這種經驗可以從設計模式的學習與應用中獲得。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章