1. 程式人生 > >【設計模式】六大原則之一(單一職責與開閉原則)

【設計模式】六大原則之一(單一職責與開閉原則)

【前言】

        最近在學習設計模式,設計模式是面向物件程式設計的。設計模式有6大原則,而實際上都是互補的,也就是說一些原則需要利用另一些原則來實現自己。下面來簡單的介紹一下六大原則其中之二

【單一職責原則】

1、單一職責原則的由來

        初學者在程式設計的時候可能一開始會有這樣的經歷,使用一個類來實現很多的功能,新新增的甚至不相關的功能都放在一個類中來實現,煮成了一鍋大雜燴,往往使得某個類包羅永珍,無所不能。可能剛開始實現的功能比較簡單,這樣做不會引起很大的問題。但是隨著之後專案複雜度的提升,各種不相關的程式碼耦合在一起,一旦有功能的更改或增刪,修改的程式碼很可能導致其他功能不能正常執行,也就是違背了單一職責原則

2、什麼是單一職責原則

        不要存在多於一個導致類變更的原因。通俗的說,就是一個類只負責一項職責

       在軟體設計中,秉承著“高內聚,低耦合”的思想,讓一個類僅負責一項職責,如果一個類有多於一項的職責,那麼就代表這個類耦合性變高了,這些職責耦合在了一起,這是比較脆弱的設計。因為一旦某一項職責發生了改變,需要去更改程式碼,那麼有可能會引起其他職責改變。所謂牽一髮而動全身,這顯然是我們所不願意看到的,所以我們會把這個類分拆開來,由兩個類來分別維護這兩個職責,這樣當一個職責發生改變,需要修改時,不會影響到另一個職責。

        單一職責原則不只是面向物件程式設計思想所特有的,只要是模組化的程式設計,都適用單一職責原則。

3、為什麼要用單一職責原則

        說到單一職責原則,很多人都不屑一顧,因為它太簡單了,這是常識。在程式設計中,誰也不希望因為修改了一個功能導致其他的功能發生故障。而避免出現這一問題的解決方法就是遵循單一職責模式。雖然單一模式如此簡單,但是也會有違背這一原則的程式碼存在。為什麼會出現這種現象?因為有職責擴散,就是因為某種原因,職責P被分化為粒度更細的職責P1和P2

        舉例說明,用一個類描述動物呼吸這個場景,程式碼如下:

       
   class Animal
   {
       public void breathe(String animal)
       {
           System.out.println(animal + "呼吸空氣");
       }
   }
   
   public class Client
   {
        public static void main(String[] args)
        {
             Animal animal=new Animal();
             animal.breathe("牛");
             animal.breathe("羊");
             animal.breathe("豬");
        }
   }
        執行結果:         牛呼吸空氣         羊呼吸空氣         豬呼吸空氣         程式上線後,發現問題了,並不是所有的動物都是呼吸空氣的,比如魚就是呼吸水的,修改時如果遵循單一職責原則,需要將Animal類細分為陸生動物類Terrestrial,水生動物Aquatic。我們會發現如果這樣修改程式碼花銷是很大的,除了要將原來的類分解之外,還需要修改客戶端,而直接修改類Animal來達成目的雖然違背了單一職責原則,但是花銷卻小很多。程式碼如下:                
   class Animal
   {
       public void breathe(String animal)
       {
           if("魚".equals(animal))
           {
               System.out.println("呼吸水");
           }
           else
           {
               System.out.println("呼吸空氣");
           }
       }
    }
   
   public class Client
   {
       public static void main(String[] args)
       {
           Animal animal = new Animal();
		   animal.breathe("牛");
		   animal.breathe("羊");
		   animal.breathe("豬");
		   animal.breathe("魚");
       }
    }
        可以看到,這種修改方式要簡單的多,但是卻存在隱患:有一天需要將魚分為呼吸淡水的魚和呼吸還睡的魚,則又需要修改Animal類的breathe方法,而對原有程式碼的修改會對呼叫“豬”“牛”“羊”等相關功能帶來風險,也許某一天你會發現程式執行的結果變為“牛呼吸水”了。這種修改方式直接在程式碼級別上違背了單一職責原則,雖然修改起來最簡單,但隱患卻是最大的。

4、遵循單一職責原則的優點

(1)降低了類的複雜度。一個類只負責一項職責比負責多項職責要簡單得多

(2)提高了程式碼的可讀性。一個類簡單了,可讀性自然就提高了

(3)提高了系統的可維護性。程式碼的可讀性高了,並且修改一項職責對其他職責影響降低了,可維護性自然就提高了

(4)變更引起的風險變低了。單一職責最大的優點就是修改一個功能,對其他功能的影響顯著降低

【開放-封閉原則】

1、什麼是開放封閉原則

        開放封閉原則是所有面向物件原則的核心。

        所謂開放封閉原則,軟體實體(類、模組、函式等等)應該是可擴充套件,而不可修改的。也就是說,對擴充套件是開放的,而對修改是封閉的

        開放封閉原則主要體現在兩個方面:

      (1)對擴充套件開放,意味著有新的需求或變化時,可以對現有程式碼進行擴充套件,以適應新的情況

      (2)對修改封閉,意味著類一旦設計完成,就可以獨立完成其工作,而不要求對類進行任何修改

2、為什麼要用開放-封閉原則 

        對於程式設計而言,如何設計才能面對需求的改變卻可以保持相對的穩定,從而可以使得系統可以在第一個版本的基礎上不斷的推出新的版本呢?答案是在程式設計的時候使用開放封閉原則。但是在設計的時候,絕對對修改的關閉是不可能的,無論模組是多麼的封閉,都存在一些無法對之封閉的變化,既然不可以完全的封閉,設計人員必須對他設計的模組應該對哪種變換的封閉做出選擇,他必須猜測出最有可能發生變換的種類,然後構造抽象來隔離那些變化

       在我們最初寫程式碼的時候,假設變化不會發生,當變化發生時我們就構造抽象類來隔離變化。當然,不是在什麼情況下應對變化都是容易的

       開放封閉原則是面向物件的核心所在,遵循這個原則可以帶來面向物件所謂的巨大的好處,也就是可維護、可擴充套件、可複用、靈活性好。然而,對於應用程式中的每個部分都刻意的抽象同樣不是一個好主意,拒絕成熟的抽象和抽象一樣重要          

3、如何使用開放封閉原則?

        實現開放封閉的核心思想就是對抽象程式設計,而不對具體程式設計,因為抽象相對穩定。讓類依賴於固定的抽象,所以對修改就是封閉的,而通過面向物件的繼承和多型機制,可以實現對抽象體的繼承,通過覆寫其方法來改變故友行為,實現新的擴充套件方法,所以對於擴充套件就是開放的

        對於違反這一原則的類,必須通過重構來進行改善。常用於實現的設計模式主要有Template Method模式和Strategy模式。而封裝變化,是實現這一原則的重要手段,將經常變化的狀態封裝為一個類

        以銀行業務員為例:

        沒有實現開放封閉原則設計的:

       
   public class BankProcess
   {
       public void Deposite(){}   //存款
       public void Withdraw(){}   //取款
       public void Transfer(){}   //轉賬
    }
   
   public class BankStaff
   {
        private BankProcess bankpro = new BankProcess();
        public void BankHandle(Client client)
        {
            switch (client .Type)
            {
                case "deposite":      //存款
                    bankpro.Deposite();
                    break;
                case "withdraw":      //取款
                    bankpro.Withdraw();
                    break;
                case "transfer":      //轉賬
                    bankpro.Transfer();
                    break;
            }
        }
    }
        這種設計顯然是存在問題的,目前設計中就只有存款、取款和轉賬這三個功能,將來如果業務增加了,比如增加中購基金功能、理財功能等,就必須要修改BankProcess業務類。我們分析上述設計就能發現不能把業務封裝在一個類裡面,違法單一職責原則,而且有新的需求發生時,必須修改現有程式碼違反了開放封閉原則         那麼,如何才能實現耦合度和靈活性兼得呢?那就是抽象,將業務功能抽象為介面,當業務員依賴於固定的抽象時,對修改就是封閉的,而通過繼承和多型繼承,從抽象體中擴展出新的實現,就是對擴充套件的開放         下面是符合開放封閉原則的設計:          
   //首先宣告一個業務處理介面
   
        這樣,當業務變更時,只需修改對應的業務實現類就可以了,其他不相干的業務就不必修改了。當業務增加,只需增加業務的實現就可以了         其實筆者認為,開閉原則無非就是想表達這樣一層意思:用抽象構建框架,用實現擴充套件細節。因為抽象靈活性好,適應性廣,只要抽象的合理,可以基本保持軟體架構的穩定。而軟體中易變的細節,我們用從抽象派生的實現類來進行擴充套件,當軟體需要發生變化時,我們只需要根據需求重新派生一個實現類來擴充套件就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預見性才行。  

       本文只是對基礎知識做一個小小的總結,不深究。如有不同,見解歡迎指正

       本文所有程式碼均已通過作者測試

       本文所有內容均為作者原創,如有轉載,請註明出處