更新於 2022/08/15閱讀時間約 38 分鐘

物件導向設計原則:SOLID

  • 單一職責原則(Single Responsibility Principle)
  • 里氏替換原則(Liskov Substitution Principle)
  • 依賴反轉原則(Dependence Inversion Principle)
  • 介面隔離原則(Interface Segregation Principle)
  • 最少知識原則(得墨忒耳定律)(Law Of Demeter)
  • 開放封閉原則(Open Closed Principle)

單一職責原則(Single Responsibility Principle)

原文定義:A class should have only one reason to change.
中文翻譯:一個模組應有且只有一個理由會使其改變。
類T負責兩個不同的職責:職責P1,職責P2。當由於職責P1需求發生改變而需要修改類T時,有可能會導致原本運行正常的職責P2功能發生故障。
👀範例程式碼:
 public class SRPShow
    {
        public static void Show()
        {
            {
                Animal animal = new AnimalChicken("雞");
                animal.Breath();
                animal.Action();
            }
            {
                Animal animal = new AnimalFish("魚");//呼吸水
                animal.Breath();
                animal.Action();
            }
        }
    }
/// <summary>
/// 封裝
/// 動物類別
/// 簡單意味者穩定
/// 
/// 都塞在一起,多個職責都在一起
/// </summary>
public  class Animal
{
    protected string _Name = null;
    public Animal(string name)
    {
        this._Name = name;
    }
    public void Breath()
    {
        if (this._Name.Equals("雞"))
        {
            Console.WriteLine($"{this._Name} 呼吸空气");
        }
        else if (this._Name.Equals("鱼"))
        {
            Console.WriteLine($"{this._Name} 呼吸水");
        }

    }
    //好多分支,好多固定分支,好多if else  三套東西
    //違背單一職責:一個類只做一件事,,一個方法也應該只做一件事
    public void Action()
    {
        if (this._Name.Equals("雞"))
        {
            Console.WriteLine($"{this._Name} flying");
        }
        else if (this._Name.Equals("魚"))
        {
            Console.WriteLine($"{this._Name} swimming");
        }
    }
}
一個類只負責一件事,拆分之後,職責變的單一
👀範例程式碼:
public class SRPShow
    {
        public static void Show()
        {
            {
                Animal animal = new AnimalChicken("雞");
                animal.Breath();
                animal.Action();
            }
            {
                Animal animal = new AnimalFish("魚");//呼吸水
                animal.Breath();
                animal.Action();
            }
        }
    }
public abstract class Animal
{
    protected string _Name = null;
    public Animal(string name)
    {
        this._Name = name;
    }
    public abstract void Breath();
    public abstract void Action();
}
/// <summary>
/// 每個類都在做自己的事
/// </summary>
public class AnimalChicken : Animal
{
    public AnimalChicken(string name) : base(name)
    {
    }
    public override void Breath()
    {
        Console.WriteLine($"{this._Name} 呼吸空气");
    }

    public override void Action()
    {
        Console.WriteLine($"{this._Name} flying");
    }
}
/// <summary>
/// 封裝
/// 動物類別
/// 簡單意味者穩定
/// </summary>
public class AnimalFish : Animal
{
    public AnimalFish(string name) : base(name)
    {
    }
    public override void Breath()
    {
        Console.WriteLine($"{this._Name} 呼吸水");
    }
    public override void Action()
    {
        Console.WriteLine($"{this._Name} swimming");
    }
}

⏯優點:
閱讀簡單,易於維護;
擴展升級時,減少修改,直接增加類別即可;
方便代碼重複使用;
⏩缺點:
單一職責成本:類別變多了;上端需要了解更多的類別

◀結論:
衡量着使用:如果類(class)相對穩定,擴充變化少,而且邏輯簡單,違背單一職責也沒關係
如果不同職責,總是一起變化,這總是一定要分開的
代碼足夠簡單,就可以稍稍違背
各使用情境:
方法:方法多個分支,還可能擴充變化,最好拆分成多個方法
類(Class):可以將類別依據功能拆分出:接受輸入-數據驗證-邏輯計算--數據庫操作--日誌(Log),為了重用,方便維護升級
介面(interface):把不同功能介面,獨立開來
類別庫:把項目拆分成多個類別庫,重用--方便維護--
項目:一個web解决所有問題,建議可以拆分例如:客戶端;管理後臺;定時服務;遠程接口;
系統:成熟的互聯網企業,有N多項目,有很多重复功能,IP庫/日誌庫/監控系統/在線統計。。。

里氏替換原則(Liskov Substitution Principle)

原文定義:Subtypes must be substitutable for their base types.
中文翻譯:子類別必須要能取代它的父類別。
白話文:父類別出現的地方,子類別就能代替它,而且要能做到替換而不出現任何錯誤或異常。

▶情境:
👀範例程式碼:
    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public void Traditional()
        {
            Console.WriteLine("仁義禮智信 溫良恭儉讓");
        }
        public void Show()
        { }
    }
    public class Chinese : People
    {
        public string Kuaizi { get; set; }
        public void SayHi()
        {
            Console.WriteLine("早上好,吃了嗎?");
        }

    }
    /// <summary>
    /// 擁有大部分的屬性和行為,但是Traditional没有
    /// </summary>
    public class Japanese : People
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public new void Traditional()
        {
            throw new Excrption();
        }
        public void Ninja()
        {
            Console.WriteLine("忍者精神");
        }
    }
因為Japanese在執行Traditional的時候會跳錯誤 因此這是沒有符合里氏替換原則
    public class LSPShow
    {
        public static void Show()
        {
            {
                Chinese people = new Chinese();
                people.Traditional();

            }
            {
                Chinese people = new Japanese();
                people.Traditional();

            }
        }
    }
🎦測驗:
問題:『A~F』分別是執行子類別還是父類別
ParentClass instance = new ChildClass();
ChildClass child = new ChildClass();

Console.WriteLine("下面是instance.CommonMethod()");
instance.CommonMethod();//『A』
typeof(ParentClass).GetMethod("CommonMethod").Invoke(instance, null);//『B』
typeof(ChildClass).GetMethod("CommonMethod").Invoke(instance, null);//『C』   
dynamic dInstance = new ChildClass();
dInstance.CommonMethod();//『D』

Console.WriteLine("下面是instance.VirtualMethod()");
instance.VirtualMethod();//『E』
Console.WriteLine("下面是instance.AbstractMethod()");
instance.AbstractMethod();//『F』
    public abstract class ParentClass
    {
        /// <summary>
        /// CommonMethod
        /// </summary>
        public void CommonMethod()
        {
            Console.WriteLine("ParentClass CommonMethod");
        }
        /// <summary>
        /// virtual  虛方法  必須包含實作 但是可以被重載
        /// </summary>
        public virtual void VirtualMethod()
        {
            Console.WriteLine("ParentClass VirtualMethod");
        }

        public virtual void VirtualMethod(string name)
        {
            Console.WriteLine("ParentClass VirtualMethod");
        }

        public abstract void AbstractMethod();
    }

    public class ChildClass : ParentClass
    {
        /// <summary>
        /// new 隱藏父類
        /// </summary>
        public new void CommonMethod()
        {
            Console.WriteLine("ChildClass CommonMethod");
        }

        /// <summary>
        /// virtual 可以被複寫
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override void VirtualMethod()
        {
            Console.WriteLine("ChildClass VirtualMethod");
            base.VirtualMethod();
        }
        public sealed override void AbstractMethod()
        {
            Console.WriteLine("ChildClass AbstractMethod");
        }
    }
答案:
『A』父類=普通方法由左邊决定,編譯時决定
『B』父類
『C』子類
『D』子類=運行時決定
『E』子類=虛方法由右邊决定,運行時決定
『F』子類=抽象方法由右邊决定,運行時決定

◀結論:
里氏替換原則:任何使用父類別的地方,都可以透明的使用其子類;繼承+不改變行為
繼承:通過繼承,子類別擁有父類別的一切屬性和行為,任何父類出現的地方,都可以用子類來代替
  • 子類必須完全實作父類別所有的方法,如果子類沒有父類的某項東西,就斷掉繼承;
  • 子類可以有父類沒有的東西,所以子類的出現的地方,不一定能用父類來代替;
  • 透明,就是安全,父類的東西換成子類后不影響程序
  • 父類已經實做的東西,子類不要去new
  • 父類已經實做的東西,想改的话,就必須用virtual+override 避免埋雷
宣告變數、參數、屬性、字段,最好都是基于父類的

最少知識原則(得墨忒耳定律)(Law Of Demeter)

原文定義:Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
中文翻譯:每個單元應該對其他單元只能有有限的知識:只了解跟目前單元比較親近的單元。
白話文: 單元與單元之間是會有耦合的。這個原則在告訴我們該如何控制耦合的程度。

▶情境:
如果要找學生資料
兩種做法:
A=班級管理自己的學生
B=學校管理 =如果足夠簡單,B作法也沒問題
👀範例程式碼:
    /// <summary>
    /// 學校
    /// </summary>
    public class School
    {
        public int Id { get; set; }
        public string SchoolName { get; set; }
        public List<Class> ClassList { get; set; }

        public void Manage()
        {
            Console.WriteLine("Manage {0}", this.GetType().Name);
            foreach (Class c in this.ClassList)
            {
                Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName);
                c.Manage();//『A』

                //List<Student> studentList = c.StudentList;//『B』
                //foreach (Student s in studentList)
                //{
                //    Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
                //}

            }
        }
    }
    /// <summary>
    /// 班級
    /// </summary>
    public class Class
    {
        public int Id { get; set; }
        public string ClassName { get; set; }

        public List<Student> StudentList { get; set; }


        public void Manage()
        {
            foreach (Student s in this.StudentList)
            {
                Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
            }
        }

    }
    /// <summary>
    /// 學生
    /// </summary>
    public class Student
    {
        public int Id { get; set; }
        public string StudentName { get; set; }
        protected int Tall { get; set; }
        private int Salary { get; set; }
    }

◀結論:
得墨忒耳定律(最少知識原則):一個物件應該對其物件保持最少的了解。
物件導向=類別=類別與類別之間會有交互=功能模塊=系統
我們在寫程式的時候會希望類跟類之間是高內聚,低耦合。
高內聚,低耦合:高度封裝,盡量少的暴露訊息,類別與類別之間減少依賴
偶合關係:【依賴】(間接朋友 ) 【關聯、組合、聚合】(直接朋友) 繼承 實現
我們可以盡量減少與間接朋友的關係,只與直接的朋友聯繫
去掉內部的依賴--降低訪問修飾符(public、private、property)
門面(外觀)模式/中介者模式

依賴反轉原則(Dependence Inversion Principle)

原文定義:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
中文翻譯:
- 高層模組不應該依賴底層模組,它們都應該依賴抽象。
- 抽象不應該依賴細節。細節應該依賴抽象。

▶情境:
依賴細節範例=學生每使用一台新手機就必須再增加一個方法
//Student高層--手機低層
Student student = new Student()
{
    Id = 191,
    Name = "老司機"
};
AbstractPhone phone = new iPhone();
student.PlayiPhone(phone);
AbstractPhone phone = new Lumia();
student.PlayLumia(phone);
```
```csharp=
 public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }

        /// <summary>
        /// 依賴細節
        /// </summary>
        /// <param name="phone"></param>
        public void PlayiPhone(iPhone phone)
        {
            Console.WriteLine("這裡是{0}", this.Name);
            phone.Call();
            phone.Text();
        }
        /// <summary>
        /// 依賴細節
        /// </summary>
        /// <param name="phone"></param>
        public void PlayLumia(Lumia phone)
        {
            Console.WriteLine("這裡是{0}", this.Name);
            phone.Call();
            phone.Text();
        }
    }
將手機抽離出抽象,這樣每次增加一支新手機只需要繼承AbstractPhone 而不用修改到Student
    public abstract class AbstractPhone
    {
        public int Id { get; set; }
        public string Branch { get; set; }
        public abstract void Call();
        public abstract void Text();
    }
    /// <summary>
    /// iPhone
    /// </summary>
    public class iPhone : AbstractPhone
    {
        public override void Call()
        {
            Console.WriteLine("User {0} Call", this.GetType().Name);
        }
        public override void Text()
        {
            Console.WriteLine("User {0} Call", this.GetType().Name);
        }
    }
    public class Lumia : AbstractPhone
    {
        public override void Call()
        {
            Console.WriteLine("User {0} Call", this.GetType().Name);
        }
        public override void Text()
        {
            Console.WriteLine("User {0} Text", this.GetType().Name);
        }
    }
public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }

        /// <summary>
        /// 依赖抽象
        /// </summary>
        /// <param name="phone"></param>
        public void Play(AbstractPhone phone)
        {
            Console.WriteLine("這裡是{0}", this.Name);
            phone.Call();
            phone.Text();
        }
    }

◀結論:
依賴反轉原則:高層模組不應該依賴底層模組,兩者應通過抽象依賴
依賴抽象,而不是依赖細節
抽象:抽象類/介面
細節:具體的類別
23種設計模式,80%以上跟這個有關
依賴細節:程式寫死了,無法進行擴展
依賴抽象,更具有通用性;而且具備擴展性;
細節多變的,抽象是穩定的;系統架構基於抽象來搭建,會更穩定更具被擴展性
面向抽象編程, 底層模組裡面盡量都有抽象類/介面,
在宣告參數/變數/屬性的時候,盡量都是 介面/抽象類
如果需求不考慮擴展變化,那確實不需要依賴反轉

介面隔離原則(Interface Segregation Principle)

原文定義:Clients should not be forced to depend on methods that they do not use.
中文翻譯:客戶不應該被強迫依賴他們不使用的方法

▶情境:
public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }
    public abstract void Call();
    public abstract void Text();
    public void Show()
    { }
}
public interface IExtend
{
    void Movie();
}
public interface IExtendGame
{
    void Online();
    void Game();
}
public interface IExtendPhotography//: IExtendGame 介面可以繼承介面 方法疊加
{
    void Photo();
    void Record();
}
public interface IExtendAdvanced
{
    void Show();
    void Map();//導航
    void Pay();
}
/// <summary>
    /// iPhone
    /// </summary>
    public class iPhone : AbstractPhone, IExtend, IExtendAdvanced, IExtendPhotography
    {
        //net框架有时候明明看到已经继承了某个接口,却找不到实现在哪里
        //實現在父類別
        public override void Call()
        {
            Console.WriteLine("User {0} Call", this.GetType().Name);
        }
        public override void Text()
        {
            Console.WriteLine("User {0} Call", this.GetType().Name);
        }

        public void Photo()//隐式实现
        {
            Console.WriteLine("User {0} Photo", this.GetType().Name);
        }

        //void IExtendAdvanced.Photo()//显示实现
        //{
        //    Console.WriteLine("User {0} Photo", this.GetType().Name);
        //}

        public void Online()
        {
            Console.WriteLine("User {0} Online", this.GetType().Name);
        }

        public void Game()
        {
            Console.WriteLine("User {0} Game", this.GetType().Name);
        }

        public void Map()
        {
            Console.WriteLine("User {0} Map", this.GetType().Name);
        }

        public void Pay()
        {
            Console.WriteLine("User {0} Pay", this.GetType().Name);
        }

        public void Record()
        {
            Console.WriteLine("User {0} Record", this.GetType().Name);
        }

        public void Movie()
        {
            Console.WriteLine("User {0} Movie", this.GetType().Name);
        }
    }

◀結論:
介面隔離原則:客戶端不應該依賴他不需要的介面;
一個類別對另一個類別的依賴應該建立在最小的介面上;
實作一個介面的時候,只需要自己必須的的功能;
實作介面,就必須把介面裡面全部方法實作
其實就是一種約束,肯定包含具體實作的
可以多重實作,所以可以拆分,然後多個實作
介面是描述能幹什麼,要求Photography,那麼你的對象就是全部可以Photography的
那就是實作這個IExtendPhotography最小介面的
為什麼不建立一個大而全的介面,而是要拆分? 因為不能讓類型實現自己沒有的功能
為什麼要盡量的用同一個介面?盡量抽象更具被重用性
介面拆分是隨者需求更新而演變的,一直演變下去,是不是直接拆分成一個介面一個方法得了??
如果真的一个介面一个方法,那也沒意義了。
究竟該如何設計呢? 確實需要豐富的經驗和對需求的了解
1 介面不能太大,否則會實現不需要的功能;
2 介面還是要盡量的小,但是一致的功能是應該在一起的,密不可分的功能不要分開
不能過度設計 要考慮清楚業務的邊界
3 介面合併:如果一個業務包含多個固定步驟,我們不應該把步驟都暴露,而是提供一個入口即可

開放封閉原則(Open Closed Principle)

原文定義:Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification.
中文翻譯:軟體實體應該對擴展開放,對修改關閉

◀結論:
開閉原則:對擴展開發,對修改關閉
如果有功能擴展變化的需求,希望是增加類而不是修改
修改會影響原有功能,引入錯誤
增加類就不會影響原有的東西
原則的原則,五大原則是手段,這個是目標

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.