深入解讀[面向對象五大設計原則]

最近在看許世偉的架構課, 面向對象五大設計原則(SOLID),扣理論找出處。

早期我跟着大家人云亦云, 回過頭來,摳字眼找出處, 五大設計原則真的很有功力。

注意區分設計原則和設計模式。
設計原則更爲抽象和泛化;
設計模式也是抽象或泛化的良好實踐,但是它們提供了更具體和實用的底層建議。

面向對象5大原則
Single Responsiblity Principle 單一職責原則
Open/Closed Principle 開閉原則
Likov Substitution Principle 里斯替代原則
Interface Segregation Principle 接口隔離原則
Dependency inversion 依賴倒置原則

單一職責原則

只能有一個讓組件或類發生改變的原因;或者說每個組件或類專注於單一功能,解決特定問題

there should never be more than one reason for a class to change. A class should be focused on a single functionality, address a specific concern.

開閉原則

對擴展開放, 對修改封閉。

擴展類的幾種方式:

  • 從類繼承
  • 類中重寫同名行爲
  • 擴展類的某些行爲

一般我們通過繼承或者實現接口來實踐開閉原則。

 	class Person
    {
        public int age;
        public string name;

        public Person(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
        public virtual void SayHallo()
        {
            Console.WriteLine("我是{0},今年{1}", name, age);
        }
    }
    class Student : Person
    {
        public string major;
        public Student(string name, int age, string major) : base(name, age)
        {
            this.major = major;
        }
        public override void SayHallo()   //子類中override重寫,實現虛方法
        {
            Console.WriteLine("我是{0},今年{1},正在學習{2}", name, age, major);
        }
    }

    class Program
    {
       static void Main(string[] args)
        {
            Person trevor1 = new Person("Trevor", 18);
            trevor1.SayHallo();
            Student trevor2 = new Student("Trevor", 18,"C#");
            trevor2.SayHallo();
        }
    }

output:
我是Trevor,今年18
我是Trevor,今年18,正在學習C#

里氏替代原則

在父子類生態中,在父類出現的地方,可以用子類對象替換父類對象,同時不改變程序的功能和正確性

。。 乍一看,這不是理所當然嗎? 爲啥單獨拎出來鞭屍,鞭策。

比如上例我們使用

  Person trevor1 = new Student("trevor",18,"C#")  // 子類對象替換父類對象
  trevor1.SayHello(); 

利用多態正確表達了含義。


但是某些情況下濫用繼承,卻不一定保證程序的正確性,會對使用者造成誤解。

比如下面經典的[矩形-正方形求面積]反例:

public class Rectangle
{
    // 分別設置寬高
    public virtual double Width {get;set;}
    public virtual double Height {get;set;}

    public virtual void Area()
    {
        Console.WriteLine("面積是:" + Width * Height);
    }
}

public class Square : Rectangle
{
    public override double Width 
    {
       // get;
        set   //  因爲是正方形,想當然重設了寬=高
        {
            base.Width= value;
            base.Height= value;
        }
    }

    public override double Height
    {
      //  get;
        set  //  因爲是正方形,想當然重設了寬=高
        {
            base.Width = value;
            base.Height = value;
        }
    }

    public override void Area()
    {
        Console.WriteLine("面積是:" + Width * Width);
    } 
}

public  class Program
{
    public static void Main()
    {
        Rectangle s = new Rectangle();
        s.Width = 2;          
        s.Height = 3;         

        s.Area();
    }
}

output:
面積是:6

但是如果你[使用子類對象去替換父類對象]:

  Rectangle s2 = new Square();
  s2.Width = 2;          
  s2.Height = 3;         
  s2.Area();

output:
面積是: 9

Get到了嗎? 並不是我們想當然就能子類對象就能無損替換父類對象的, 根本原因是我們正方形雖然是(is a)矩形,但是我們的重寫行爲破壞了父類的表達,這是一種繼承的誤用。

里氏替代原則就是約束你在繼承的時候注意到這個現象,並提醒你規避這個問題。

這個時候,不應該重寫父類的SetWight方法, 而應該擴展新的方法SetLength。

接口隔離

接口隔離,將胖接口修改爲多個小接口,調用接口的代碼應該比實現接口的代碼更依賴於接口

why:
如果一個類實現了胖接口的所有方法(部分方法在某次調用時並不需要),那麼在該次調用時我們就會發現此時出現了(部分並不需要的方法),而並沒有機制告訴我們我們現在不應該使用這部分方法。
how:
避免胖接口,不要實現違反單一職責原則的接口。可以根據實際多職責劃分爲多接口,類實現多接口後, 在調用時以特定接口指代對象,這樣這個對象只能體現特定接口的方法,以此體現接口隔離。

   public interface IA
    {
        void getA();
    }

    interface IB
    {
        void getB();
    }

    public class Test : IA, IB
    {
        public string Field { get; set; }
        public void getA()
        {
            throw new NotImplementedException();
        }

        public void getB()
        {
            throw new NotImplementedException();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            IA a = new Test();
            a.getA();       //  在這個調用處只能看到接口IA的方法, 接口隔離
        }
    }

依賴倒置原則

實現依賴於抽象, 抽象不依賴於細節

Q:這個原則我其實一開始沒能理解什麼叫“倒置”?

A: 但有了一點開發經驗後開始有點心得了。

痛點:面向過程的開發,上層調用下層,上層依賴於下層。當下層變動時上層也要跟着變動,導致模塊複用度降低,維護成本增高。

提煉痛點: 含有高層策略的模塊,如AutoSystem模塊,依賴於它所控制的低層的負責具體細節的模塊。

思路:找到一種方法使AutoSystem模塊獨立於它所控制的具體細節,那麼我們就可以自由地複用AutoSystem了; 同時讓底層汽車廠也依賴抽象,受抽象驅動,這就形成一種“倒置”。

所以依賴倒置原則有兩個關鍵體現:
① 高層次模塊不應該依賴於底層實現,而都應該依賴於抽象;

這在上圖: AutoSystem和Car都依賴於抽象接口ICar

② 抽象不應該依賴於具體實現,具體實現應該依賴於抽象。

第2點與第1點不是重複的,這一點意味着細節實現是受抽象驅動,這也是“倒置”的由來。

五大設計原則SOLID,是指導思想,不貫徹這5大設計原則也能讓程序跑起來,但是可能就會出現可閱讀性、維護性、正確性問題。

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