一個優秀的Unity3d開發者必備的幾種設計模式

Unity腳本編程

衆所周知,unity的編程屬於腳本化,腳本沒有一個具體的概念跟架構, 致使在項目過程當中,常常出現哪裏須要實現什麼功能,就隨便添加腳本,
結果,就形成了一片混亂,很差管理。 html


更有甚者,本身的寫的代碼閒置一段時間後,再去想找某個功能的實現,都要在視圖中翻來覆去找半天。
哎!請允許我在此感嘆一聲,這仍是你寫的東西麼?
所以,一個好的設計模式是多麼的重要啊, 程序員

如何寫腳本架構

那麼,咱們在使用unity3d開發東西的時候,腳本架構到底應該如何來寫?
呵呵... 編程


其實,我也給不了大傢俱體答案,由於每一個人的開發習慣,每一個團隊的開發模式也各有千秋,
so,在此我只作幾種設計模式的總結, 設計模式

參考書籍

主要參考書籍有《設計模式》《設計模式之禪》《大話設計模式》以及網上一些零散的文章,
但主要內容仍是我本人的一些經驗以及感悟。
寫出來的目的一方面是系統地整理一下,一方面也與廣大的網友分享,
至於大家到底如何使用,
望君斟酌啊! 架構

設計模式

設計模式對編程人員來講,的確很是重要。
固然,若是你們的理解跟我有所不一樣,歡迎留言,你們共同探討。 框架

  • 原則1:單一職責
  • 原則2:里氏替換原則(子類擴展但不改變父類功能)
  • 原則3:依賴倒置原則
  • 原則4:接口隔離原則
  • 原則5:迪米特法則(最少知道原則)
  • 原則6:開閉原則

    原則1:單一職責原則

    說到單一職責原則,不少人都會不屑一顧。
    由於它太簡單了,稍有經驗的程序員即便歷來沒有讀過設計模式、歷來沒有據說過單一職責原則,在設計軟件時也會自覺的遵照這一重要原則,由於這是常識。
    在軟件編程中,誰也不但願由於修改了一個功能致使其餘的功能發生故障。
    而避免出現這一問題的方法即是遵循單一職責原則。
    雖然單一職責原則如此簡單,而且被認爲是常識,可是即使是經驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在。
    爲何會出現這種現象呢?由於有職責擴散。所謂職責擴散,就是由於某種緣由,職責被分化成了更細的職責。 模塊化

    用一個類描述動物呼吸這個場景

    class Animal
    {
    
        public void breathe(string animal)
        {
            Debug.Log(animal + "呼吸空氣");
        }
    }
    
    public class Client
    {
        Animal animal = new Animal();
    
        void Start()
        {
    
            animal.breathe("");
            animal.breathe("");
            animal.breathe("");
        }
    }
    
    //運行結果:
        //牛呼吸空氣
        //羊呼吸空氣
        //豬呼吸空氣

    當需求變更

    程序上線後,發現問題了,並非全部的動物都呼吸空氣的,好比魚就是呼吸水的。函數

    修改時若是遵循單一職責原則,須要將Animal類細分爲陸生動物類Terrestrial,水生動物Aquatic,代碼以下:測試

    class Terrestrial
    {
        public void breathe(String animal)
        {
            Debug.Log(animal + "呼吸空氣");
        }
    }
    
    class Aquatic
    {
        public void breathe(String animal)
        {
            Debug.Log(animal + "呼吸水");
        }
    }
    
    public class Client
    {
        public static void main(String[] args)
        {
            Terrestrial terrestrial = new Terrestrial();
            terrestrial.breathe("");
            terrestrial.breathe("");
            terrestrial.breathe("");
    
            Aquatic aquatic = new Aquatic();
            aquatic.breathe("");
        }
    }
    
    //運行結果:
        //牛呼吸空氣
        //羊呼吸空氣
        //豬呼吸空氣
        //魚呼吸水

    改動量小的方法

    咱們會發現若是這樣修改花銷是很大的,除了將原來的類分解以外,還須要修改客戶端。
    而直接修改類Animal來達成目的雖然違背了單一職責原則,但花銷卻小的多,代碼以下:字體

    class Animal
    {
        public void breathe(String animal)
        {
            if ("" == animal)
            {
                Debug.Log((animal + "呼吸水"));
            }
            else
            {
                Debug.Log((animal + "呼吸空氣"));
            }
        }
    }
    
    public class Client
    {
        public static void main(String[] args)
        {
            Animal animal = new Animal();
            animal.breathe("");
            animal.breathe("");
            animal.breathe("");
            animal.breathe("");
        }
    }

    隱患

    能夠看到,這種修改方式要簡單的多。
    可是卻存在着隱患:有一天須要將魚分爲呼吸淡水的魚和呼吸海水的魚,
    則又須要修改Animal類的breathe方法,而對原有代碼的修改會對調用「豬」「牛」「羊」等相關功能帶來風險,
    也許某一天你會發現程序運行的結果變爲「牛呼吸水」了。
    這種修改方式直接在代碼級別上違背了單一職責原則,雖然修改起來最簡單,但隱患倒是最大的。

    另外一種修改方式

    class Animal
    {
        public void breathe(String animal)
        {
            Debug.Log(animal + "呼吸空氣");
        }
    
        public void breathe2(String animal)
        {
            Debug.Log(animal + "呼吸水");
        }
    }
    
    public class Client
    {
        public static void main(String[] args)
        {
            Animal animal = new Animal();
            animal.breathe("");
            animal.breathe("");
            animal.breathe("");
            animal.breathe2("");
        }
    }

    能夠看到,這種修改方式沒有改動原來的方法,而是在類中新加了一個方法,這樣雖然也違背了單一職責原則,
    但在方法級別上倒是符合單一職責原則的,由於它並無動原來方法的代碼。這三種方式各有優缺點,
    那麼在實際編程中,採用哪一中呢?
    其實這真的比較難說,須要根據實際狀況來肯定。
    個人原則是:只有邏輯足夠簡單,才能夠在代碼級別上違反單一職責原則;只有類中方法數量足夠少,才能夠在方法級別上違反單一職責原則。

    遵循單一職責原的優勢有

  • 能夠下降類的複雜度,一個類只負責一項職責,其邏輯確定要比負責多項職責簡單的多;
  • 提升類的可讀性,提升系統的可維護性;
  • 變動引發的風險下降,變動是必然的,若是單一職責原則遵照的好,當修改一個功能時,能夠顯著下降對其餘功能的影響。

    須要說明的一點是單一職責原則不僅是面向對象編程思想所特有的,只要是模塊化的程序設計,都適用單一職責原則。

    原則2:里氏替換原則

    名字的由來

    確定有很多人跟我剛看到這項原則的時候同樣,對這個原則的名字充滿疑惑。
    其實緣由就是這項原則最先是在1988年,由麻省理工學院的一位姓裏的女士(Barbara Liskov)提出來的。
    簡單來講的話,就是當咱們使用繼承時,遵循里氏替換原則。

    定義

    注:類B繼承類A時,除添加新的方法完成新增功外,儘可能不要重寫父類A的方法,也儘可能不要重載父類A的方法。
    繼承包含這樣一層含義:父類中凡是已經實現好的方法(相對於抽象方法而言),其實是在設定一系列的規範和契約,
    雖然它不強制要求全部的子類必須聽從這些契約,可是若是子類對這些非抽象方法任意修改,
    就會對整個繼承體系形成破壞。而里氏替換原則就是表達了這一層含義。
    繼承做爲面向對象三大特性之一,在給程序設計帶來巨大便利的同時,也帶來了弊端。
    好比使用繼承會給程序帶來侵入性,程序的可移植性下降,增長了對象間的耦合性,若是一個類被其餘的類所繼承,
    則當這個類須要修改時,必須考慮到全部的子類,而且父類修改後,
    全部涉及到子類的功能都有可能會產生故障。

    繼承的風險

    那就讓咱們一塊兒看看繼承的風險,以下:

    class A
    {
        public int func1(int a, int b)
        {
            return a - b;
        }
    }
    
    public class Client
    {
        void Start()
        {
            A a = new A();
            Debug.Log("100-50=" + a.func1(100, 50));
            Debug.Log("100-80=" + a.func1(100, 80));
        }
    }

    運行結果

    100-50=50
    100-80=20

    需求變更

    後來,咱們須要增長一個新的功能:完成兩數相加,而後再與100求和,由類B來負責。
    即類B須要完成兩個功能:
    兩數相減。
    兩數相加,而後再加100。
    因爲類A已經實現了第一個功能,因此類B繼承類A後,只須要再完成第二個功能就能夠了,代碼以下

    class B:A
    {
        public int func1(int a, int b)
        {
            return a + b;
        }
    
        public int func2(int a, int b)
        {
            return func1(a, b) + 100;
        }
    }
    
    public class Client
    {
        private void Start()
        {
            B b = new B();
            Debug.Log("100-50=" + b.func1(100, 50));
            Debug.Log("100-80=" + b.func1(100, 80));
            Debug.Log("100+20+100=" + b.func2(100, 20));
        }
    }

    類B運行結果

    100-50=150
    100-80=180
    100+20+100=220

    影響了正常的功能

    咱們發現本來運行正常的相減功能發生了錯誤。
    緣由就是類B在給方法起名時無心中重寫了父類的方法,形成全部運行相減功能的代碼所有調用了類B重寫後的方法,形成本來運行正常的功能出現了錯誤。
    在本例中,引用基類A完成的功能,換成子類B以後,發生了異常。
    在實際編程中,咱們經常會經過重寫父類的方法來完成新的功能,這樣寫起來雖然簡單,
    可是整個繼承體系的可複用性會比較差,特別是運用多態比較頻繁時,程序運行出錯的概率很是大。
    若是非要重寫父類的方法,比較通用的作法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴、聚合,組合等關係代替。

    里氏替換原則通俗的來說就是

    子類能夠擴展父類的功能,但不能改變父類原有的功能。它包含如下4層含義:
    1.子類能夠實現父類的抽象方法,但不能覆蓋父類的非抽象方法。
    2.子類中能夠增長本身特有的方法。
    3.當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。
    4.當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。


    看上去很難以想象,由於咱們會發如今本身編程中經常會違反里氏替換原則,程序照樣跑的好好的。
    因此你們都會產生這樣的疑問,假如我非要不遵循里氏替換原則會有什麼後果?
    後果就是:你寫的代碼出問題的概率將會大大增長。

    原則3:依賴倒置原則

    定義

    高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該依賴細節;細節應該依賴抽象。

    以抽象爲基礎搭建起來的架構比以細節爲基礎搭建起來的架構要穩定的多。
    抽象指的是接口或者抽象類,細節就是具體的實現類,使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操做,把展示細節的任務交給他們的實現類去完成。

    依賴倒置原則核心思想

    依賴倒置原則的核心思想是面向接口編程,咱們依舊用一個例子來講明面向接口編程比相對於面向實現編程好在什麼地方。

    情景舉例

    場景是這樣的,母親給孩子講故事,只要給她一本書,她就能夠照着書給孩子講故事了。代碼以下:

    class Book
    {
        public String getContent()
        {
            return "好久好久之前有一個阿拉伯的故事……";
        }
    }
    
    class Mother
    {
        public void narrate(Book book)
        {
            Debug.Log("媽媽開始講故事");
            Debug.Log(book.getContent());
        }
    }
    
    public class Client
    {
        void Start()
        {
            Mother mother = new Mother();
            mother.narrate(new Book());
        }
    }

    運行結果:

    媽媽開始講故事
    好久好久之前有一個阿拉伯的故事……

    需求變更

    運行良好,假若有一天,需求變成這樣:不是給書而是給一份報紙,讓這位母親講一下報紙上的故事,報紙的代碼以下:

    class Newspaper
    {
        public String getContent()
        {
            return "林書豪38+7領導尼克斯擊敗湖人……";
        }
    }

    這位母親卻辦不到,由於她竟然不會讀報紙上的故事,這太荒唐了,只是將書換成報紙,竟然必需要修改Mother才能讀。
    假如之後需求換成雜誌呢?換成網頁呢?
    還要不斷地修改Mother,這顯然不是好的設計。
    緣由就是Mother與Book之間的耦合性過高了,必須下降他們之間的耦合度才行。

    抽象的接口

    咱們引入一個抽象的接口IReader。
    讀物,只要是帶字的都屬於讀物:

    interface IReader
    {
        String getContent();
    }

    Mother類與接口IReader發生依賴關係,而Book和Newspaper都屬於讀物的範疇,
    他們各自都去實現IReader接口,這樣就符合依賴倒置原則了,代碼修改成:

    interface IReader
    {
        String getContent();
    }
    
    class Newspaper : IReader
    {
        public String getContent()
        {
            return "林書豪17+9助尼克斯擊敗老鷹……";
        }
    }
    class Book : IReader
    {
        public String getContent()
        {
            return "好久好久之前有一個阿拉伯的故事……";
        }
    }
    
    class Mother
    {
        public void narrate(IReader reader)
        {
            Debug.Log("媽媽開始講故事");
            Debug.Log(reader.getContent());
        }
    }
    
    public class Client
    {
        public static void main(String[] args)
        {
            Mother mother = new Mother();
            mother.narrate(new Book());
            mother.narrate(new Newspaper());
        }
    }

    運行結果

    媽媽開始講故事
    好久好久之前有一個阿拉伯的故事……
    媽媽開始講故事
    林書豪17+9助尼克斯擊敗老鷹……


    這樣修改後,不管之後怎樣擴展Client類,都不須要再修改Mother類了。
    這只是一個簡單的例子,實際狀況中,表明高層模塊的Mother類將負責完成主要的業務邏輯,一旦須要對它進行修改,引入錯誤的風險極大。
    因此遵循依賴倒置原則能夠下降類之間的耦合性,提升系統的穩定性,下降修改程序形成的風險。
    採用依賴倒置原則給多人並行開發帶來了極大的便利,


    好比上例中,本來Mother類與Book類直接耦合時,Mother類必須等Book類編碼完成後才能夠進行編碼,由於Mother類依賴於Book類。
    修改後的程序則能夠同時開工,互不影響,由於Mother與Book類一點關係也沒有。
    參與協做開發的人越多、項目越龐大,採用依賴致使原則的意義就越重大。
    如今很流行的TDD開發模式就是依賴倒置原則最成功的應用。

    在實際編程中,咱們通常須要作到以下3點

    1.低層模塊儘可能都要有抽象類或接口,或者二者都有。
    2.變量的聲明類型儘可能是抽象類或接口。使用繼承時遵循里氏替換原則。
    3.依賴倒置原則的核心就是要咱們面向接口編程,理解了面向接口編程,也就理解了依賴倒置。

    原則4:接口隔離原則

    定義

    客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上。
    將臃腫的接口I拆分爲獨立的幾個接口,類A和類C分別與他們須要的接口創建依賴關係。也就是採用接口隔離原則。
    舉例來講明接口隔離原則:

    未遵循接口隔離原則的設計

    類圖1 
    這個圖的意思是:類A依賴接口I中的方法一、方法二、方法3,類B是對類A依賴的實現。
    類C依賴接口I中的方法一、方法四、方法5,類D是對類C依賴的實現。
    對於類B和類D來講,雖然他們都存在着用不到的方法(也就是圖中紅色字體標記的方法),但因爲實現了接口I,因此也必需要實現這些用不到的方法。

    示例代碼

    對類圖不熟悉的能夠參照程序代碼來理解,代碼以下:

    //接口
    interface I
    {
        void method1();
        void method2();
        void method3();
        void method4();
        void method5();
    }
    
    class A
    {
        public void depend1(I i)
        {
            i.method1();
        }
        public void depend2(I i)
        {
            i.method2();
        }
        public void depend3(I i)
        {
            i.method3();
        }
    }
    
    class B : I
    {
        public void method1()
        {
            Debug.Log("類B實現接口I的方法1");
        }
        public void method2()
        {
            Debug.Log("類B實現接口I的方法2");
        }
        public void method3()
        {
            Debug.Log("類B實現接口I的方法3");
        }
        //對於類B來講,method4和method5不是必需的,可是因爲接口A中有這兩個方法,
        //因此在實現過程當中即便這兩個方法的方法體爲空,也要將這兩個沒有做用的方法進行實現。
        public void method4() { }
        public void method5() { }
    }
    
    class C
    {
        public void depend1(I i)
        {
            i.method1();
        }
        public void depend2(I i)
        {
            i.method4();
        }
        public void depend3(I i)
        {
            i.method5();
        }
    }
    
    class D : I
    {
        public void method1()
        {
            Debug.Log("類D實現接口I的方法1");
        }
        //對於類D來講,method2和method3不是必需的,可是因爲接口A中有這兩個方法,
        //因此在實現過程當中即便這兩個方法的方法體爲空,也要將這兩個沒有做用的方法進行實現。
        public void method2() { }
        public void method3() { }
    
        public void method4()
        {
            Debug.Log("類D實現接口I的方法4");
        }
        public void method5()
        {
            Debug.Log("類D實現接口I的方法5");
        }
    }
    
    public class Client
    {
        void Start()
        {
            A a = new A();
            a.depend1(new B());
           a.depend2(new B());
           a.depend3(new B());
    
            C c = new C();
          c.depend1(new D()));
            c.depend2(new D());
            c.depend3(new D());
        }
    }

    能夠看到,若是接口過於臃腫,只要接口中出現的方法,無論對依賴於它的類有沒有用處,實現類中都必須去實現這些方法,這顯然不是好的設計。
    若是將這個設計修改成符合接口隔離原則,就必須對接口I進行拆分。

    遵循接口隔離原則的設計

    在這裏咱們將原有的接口I拆分爲三個接口,拆分後的設計如圖2所示:

    類圖2

    示例代碼

    照例貼出程序的代碼,供不熟悉類圖的朋友參考:

    interface I1
    {
         void method1();
    }
    
    interface I2
    {
         void method2();
         void method3();
    }
    
    interface I3
    {
         void method4();
         void method5();
    }
    
    class A
    {
        public void depend1(I1 i)
        {
            i.method1();
        }
        public void depend2(I2 i)
        {
            i.method2();
        }
        public void depend3(I2 i)
        {
            i.method3();
        }
    }
    
    class B : I1, I2
    {
        public void method1()
        {
            Debug.Log("類B實現接口I1的方法1");
        }
        public void method2()
        {
            Debug.Log("類B實現接口I2的方法2");
        }
        public void method3()
        {
            Debug.Log("類B實現接口I2的方法3");
        }
    }
    
    class C
    {
        public void depend1(I1 i)
        {
            i.method1();
        }
        public void depend2(I3 i)
        {
            i.method4();
        }
        public void depend3(I3 i)
        {
            i.method5();
        }
    }
    
    class D : I1, I3
    {
        public void method1()
        {
            Debug.Log("類D實現接口I1的方法1");
        }
        public void method4()
        {
            Debug.Log("類D實現接口I3的方法4");
        }
        public void method5()
        {
            Debug.Log("類D實現接口I3的方法5");
        }
    }

    接口隔離原則的含義是:創建單一接口,不要創建龐大臃腫的接口,儘可能細化接口,接口中的方法儘可能少。
    也就是說,咱們要爲各個類創建專用的接口,而不要試圖去創建一個很龐大的接口供全部依賴它的類去調用。
    本文例子中,將一個龐大的接口變動爲3個專用的接口所採用的就是接口隔離原則。


    在程序設計中,依賴幾個專用的接口要比依賴一個綜合的接口更靈活。
    接口是設計時對外部設定的「契約」,經過分散定義多個接口,能夠預防外來變動的擴散,提升系統的靈活性和可維護性。
    說到這裏,不少人會覺的接口隔離原則跟以前的單一職責原則很類似,其實否則。
    其一,單一職責原則原注重的是職責;而接口隔離原則注重對接口依賴的隔離。
    其二,單一職責原則主要是約束類,其次纔是接口和方法,它針對的是程序中的實現和細節;

    而接口隔離原則主要約束接口,主要針對抽象,針對程序總體框架的構建。

    注意幾點

    採用接口隔離原則對接口進行約束時,要注意如下幾點:
    1.接口儘可能小,可是要有限度。對接口進行細化能夠提升程序設計靈活性是不掙的事實,可是若是太小,則會形成接口數量過多,使設計複雜化。因此必定要適度。
    2.爲依賴接口的類定製服務,只暴露給調用的類它須要的方法,它不須要的方法則隱藏起來。只有專一地爲一個模塊提供定製服務,才能創建最小的依賴關係。
    3.提升內聚,減小對外交互。使接口用最少的方法去完成最多的事情。
    運用接口隔離原則,必定要適度,接口設計的過大或太小都很差。設計接口的時候,只有多花些時間去思考和籌劃,才能準確地實踐這一原則。

     

    原則5:迪米特法則

    定義

    一個對象應該對其餘對象保持最少的瞭解
    類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另外一個類的影響也越大。
    所以,儘可能下降類與類之間的耦合。
    自從咱們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內聚。
    不管是面向過程編程仍是面向對象編程,只有使各個模塊之間的耦合儘可能的低,才能提升代碼的複用率。
    低耦合的優勢不言而喻,可是怎麼樣編程才能作到低耦合呢?那正是迪米特法則要去完成的。

    最少知道原則

    迪米特法則又叫最少知道原則,最先是在1987年由美國Northeastern University的Ian Holland提出。
    通俗的來說,就是一個類對本身依賴的類知道的越少越好。也就是說,對於被依賴的類來講,不管邏輯多麼複雜,都儘可能地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外泄漏任何信息。
    迪米特法則還有一個更簡單的定義:只與直接的朋友通訊。首先來解釋一下什麼是直接的朋友:
    每一個對象都會與其餘對象有耦合關係,只要兩個對象之間有耦合關係,咱們就說這兩個對象之間是朋友關係。
    耦合的方式不少,依賴、關聯、組合、聚合等。其中,咱們稱出現成員變量、方法參數、方法返回值中的類爲直接的朋友,
    而出如今局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要做爲局部變量的形式出如今類的內部。

    違反迪米特法則的設計

    舉一個例子:有一個集團公司,下屬單位有分公司和直屬部門,如今要求打印出全部下屬單位的員工ID。
    先來看一下違反迪米特法則的設計。

    //總公司員工
    class Employee
    {
        private String id;
        public void setId(String id)
        {
            this.id = id;
        }
        public String getId()
        {
            return id;
        }
    }
    
    //分公司員工
    class SubEmployee
    {
        private String id;
        public void setId(String id)
        {
            this.id = id;
        }
        public String getId()
        {
            return id;
        }
    }
    
    class SubCompanyManager
    {
        public List<SubEmployee> getAllEmployee()
        {
            List<SubEmployee> list = new List<SubEmployee>();
            for (int i = 0; i < 100; i++)
            {
                SubEmployee emp = new SubEmployee();
                //爲分公司人員按順序分配一個ID
                emp.setId("分公司" + i);
                list.Add(emp);
            }
            return list;
        }
    }
    
    class CompanyManager
    {
    
        public List<Employee> getAllEmployee()
        {
            List<Employee> list = new List<Employee>();
            for (int i = 0; i < 30; i++)
            {
                Employee emp = new Employee();
                //爲總公司人員按順序分配一個ID
                emp.setId("總公司" + i);
                list.Add(emp);
            }
            return list;
        }
    
        public void printAllEmployee(SubCompanyManager sub)
        {
            List<SubEmployee> list1 = sub.getAllEmployee();
            foreach (SubEmployee e in list1)
            {
                Debug.Log(e.getId());
            }
    
    
            List<Employee> list2 = this.getAllEmployee();
            foreach (Employee e in list2)
            {
                Debug.Log(e.getId());
            }
        }
    }
    
    public class Client
    {
        void Start()
        {
            CompanyManager e = new CompanyManager();
            e.printAllEmployee(new SubCompanyManager());
        }
    }

    如今這個設計的主要問題出在CompanyManager中,根據迪米特法則,只與直接的朋友發生通訊,
    而SubEmployee類並非CompanyManager類的直接朋友(以局部變量出現的耦合不屬於直接朋友),從邏輯上講總公司只與他的分公司耦合就好了,
    與分公司的員工並無任何聯繫,這樣設計顯然是增長了沒必要要的耦合。

    修改後的代碼

    按照迪米特法則,應該避免類中出現這樣非直接朋友關係的耦合。修改後的代碼以下:

    class SubCompanyManager
    {
        public List<SubEmployee> getAllEmployee()
        {
            List<SubEmployee> list = new List<SubEmployee>();
            for (int i = 0; i < 100; i++)
            {
                SubEmployee emp = new SubEmployee();
                //爲分公司人員按順序分配一個ID
                emp.setId("分公司" + i);
                list.Add(emp);
            }
            return list;
        }
        public void printEmployee()
        {
            List<SubEmployee> list = this.getAllEmployee();
            foreach (SubEmployee e in list)
            {
                Debug.Log(e.getId());
            }
        }
    }
    
    class CompanyManager
    {
        public List<Employee> getAllEmployee()
        {
            List<Employee> list = new List<Employee>();
            for (int i = 0; i < 30; i++)
            {
                Employee emp = new Employee();
                //爲總公司人員按順序分配一個ID
                emp.setId("總公司" + i);
                list.Add(emp);
            }
            return list;
        }
    
        public void printAllEmployee(SubCompanyManager sub)
        {
            sub.printEmployee();
            List<Employee> list2 = this.getAllEmployee();
            foreach (Employee e in list2)
            {
                Debug.Log(e.getId());
            }
        }
    }

    主要的變化

    image

    修改後,爲分公司增長了打印人員ID的方法,總公司直接調用來打印,從而避免了與分公司的員工發生耦合。

    總結

    迪米特法則的初衷是下降類之間的耦合,因爲每一個類都減小了沒必要要的依賴,所以的確能夠下降耦合關係。
    可是凡事都有度,雖然能夠避免與非直接的類通訊,可是要通訊,必然會經過一個「中介」來發生聯繫,例如本例中,
    總公司就是經過分公司這個「中介」來與分公司的員工發生聯繫的。
    過度的使用迪米特原則,會產生大量這樣的中介和傳遞類,致使系統複雜度變大。
    因此在採用迪米特法則時要反覆權衡,既作到結構清晰,又要高內聚低耦合。

     

    原則6:開閉原則

    定義

    一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉

    在軟件的生命週期內,由於變化、升級和維護等緣由須要對軟件原有代碼進行修改時,
    可能會給舊代碼中引入錯誤,也可能會使咱們不得不對整個功能進行重構,而且須要原有代碼通過從新測試。
    所以,當軟件須要變化時,儘可能經過擴展軟件實體的行爲來實現變化,而不是經過修改已有的代碼來實現變化。
    閉原則是面向對象設計中最基礎的設計原則,它指導咱們如何創建穩定靈活的系統。開閉原則多是設計模式六項原則中定義最模糊的一個了,

    它只告訴咱們對擴展開放,對修改關閉,但是到底如何才能作到對擴展開放,對修改關閉,並無明確的告訴咱們。
    之前,若是有人告訴我「你進行設計的時候必定要遵照開閉原則」,我會覺的他什麼都沒說,但貌似又什麼都說了。由於開閉原則真的太虛了。
    在仔細思考以及仔細閱讀不少設計模式的文章後,終於對開閉原則有了一點認識。
    其實,咱們遵循設計模式前面5大原則,以及使用23種設計模式的目的就是遵循開閉原則。

    如何遵照

    也就是說,只要咱們對前面5項原則遵照的好了,設計出的軟件天然是符合開閉原則的,這個開閉原則更像是前面五項原則遵照程度的「平均得分」,
    前面5項原則遵照的好,平均分天然就高,說明軟件設計開閉原則遵照的好;
    若是前面5項原則遵照的很差,則說明開閉原則遵照的很差。
    其實,開閉原則無非就是想表達這樣一層意思:用抽象構建框架,用實現擴展細節。
    由於抽象靈活性好,適應性廣,只要抽象的合理,能夠基本保持軟件架構的穩定。
    而軟件中易變的細節,咱們用從抽象派生的實現類來進行擴展,當軟件須要發生變化時,咱們只須要根據需求從新派生一個實現類來擴展就能夠了。
    固然前提是咱們的抽象要合理,要對需求的變動有前瞻性和預見性才行。

     

    如何去遵照這六個原則

    對這六個原則的遵照並非 是和否的問題,而是多和少的問題,也就是說,咱們通常不會說有沒有遵照,而是說遵照程度的多少。
    任何事都是過猶不及,設計模式的六個設計原則也是同樣,制定這六個原則的目的並非要咱們刻板的遵照他們,而須要根據實際狀況靈活運用。
    對他們的遵照程度只要在一個合理的範圍內,就算是良好的設計。
    若是你們對這六項原則的理解跟我有所不一樣,歡迎指正

    說明

    本文轉載自並對其整理目錄:[ζއ隨風去旅行] Unity3d 一個優秀的程序必備的幾種設計模式

相關文章
相關標籤/搜索