單一職責原則 開閉原則 里氏轉換原則 介面隔離原則 依賴倒轉原則單一職責原則
簡單點理解,型別應當只具有一種功能,功能要單一,例如操作資料庫的類則不應該再有其他方法,比如對產品的管理之類的操作。不僅僅是類,方法也是如此,每個方法專注一件事。
開閉原則擴充套件開放,更改關閉。即不修改來擴充套件類的行為。比如透過繼承介面來實現開閉原則,簡單工廠和工廠方法的一個區別就是這樣。
簡單工廠設計模式
即使用一個工廠類來建立其他類的例項,透過switch根據不同引數返回不同的例項,其他類都有一個共同的父類。
有三個要素:
工廠:負責建立例項
抽象:即我們要建立的物件的父類
具體物件:我們需要建立的物件,即具體類的例項
其中:
1. 抽象出來的父類定義子類共有的方法
2. 子類的不同實現由自己完成
3. 工廠類用於建立例項物件,具體要建立哪個由傳入的引數決定,可以透過switch判斷。在這一步中,就不符合了開閉原則了。
class Program { static void Main(string[] args) { Factory f = new Factory(); f.CreateGun("pistol"); f.CreateGun("rifle"); Console.ReadKey(); } } /// <summary> /// 父類槍 /// </summary> public class Gun { } /// <summary> /// 步槍 /// </summary> public class Rifle : Gun { public Rifle() { Console.WriteLine("步槍"); } } /// <summary> /// 手槍 /// </summary> public class Pistol : Gun { public Pistol() { Console.WriteLine("手槍"); } } /// <summary> /// 工廠類 /// </summary> public class Factory { public Gun CreateGun(string type) { // 因為這個switch使得簡單工廠不符合開閉原則,很難擴充套件 switch (type) { case "pistol": return new Pistol(); case "rifle": return new Rifle(); default: throw new ArgumentException("暫無該項武器..."); } } }
簡單工廠主要問題就是擴充套件性很差,但需求改變時,比如增加具體的子類武器,我們需要建立一個新的子類並且繼承父類,然後工廠類中也需要新增判斷,這違背了開閉原則。業務簡單或者需求變動少產品增加少的情況下簡單工廠還是比較合適使用的,但是複雜的業務環境肯定就不是首選。
工廠方法模式
類的例項化由子類來決定。
和簡單工廠不同:
1. 簡單工廠只有一個工廠,例項化都在這個工廠中,透過傳入引數判斷,它可以建立所有的東西。而工廠方法有一個抽象的工廠和多個具體的工廠,每個具體的工廠建立對應的子類例項,例項化是在具體的工廠中實現的,所以擴充套件的時候很方便,不用修改switch裡面的判斷,只需要增加工廠即可。
2. 工廠方法符合開閉原則,簡單工廠不符合。
要素:
1. 抽象產品
2. 具體產品
3. 抽象工廠(簡單工廠沒有抽象工廠)
4. 具體工廠
class Program { static void Main(string[] args) { AbstractFactory f = new PistolFactory(); f.Create(); Console.ReadKey(); } } /// <summary> /// 父類槍 /// </summary> public class Gun { } /// <summary> /// 步槍 /// </summary> public class Rifle : Gun { public Rifle() { Console.WriteLine("步槍"); } } /// <summary> /// 手槍 /// </summary> public class Pistol : Gun { public Pistol() { Console.WriteLine("手槍"); } } /// <summary> /// 抽象工廠 /// </summary> public interface AbstractFactory { /// <summary> /// 建立物件的介面方法 /// </summary> /// <returns></returns> Gun Create(); } /// <summary> /// 步槍的具體工廠 /// </summary> public class RifleFactory : AbstractFactory { public Gun Create() { return new Rifle(); } } /// <summary> /// 手槍的具體工廠 /// </summary> public class PistolFactory : AbstractFactory { public Gun Create() { return new Pistol(); } }
此時,如果增加了新的槍類,我們只需要增加具體的子類槍物件,繼承抽象的父類,抽象工廠無需改動,再增加一個具體的工廠繼承抽象工廠介面用來建立這個新產品即可。
說白了就是原有的程式碼無需修改,只需要新增,這一點符合開閉原則,引進新產品的時候非常的方便。
里氏轉換原則1)、子類可以賦值給父類(需要一個父類型別時,給一個子類型別的物件是可以的)
Person p1 = new Student();
2)、如果父類中裝的是子類物件,那麼可以將這個父類強轉為這個子類物件。
Person p1 = new Student();Student s1 = (Student)p1;
(不能把父類型別轉換成子類物件):
Chinese c = (Chinese)new Person(); //是錯的
我們子類繼承了父類,就希望子類可以做父類的所有行為,並且自己可以擴充套件父類不能做的行為,實際上,子類並不一定都能做到父類的所有行為,例如鴨有吃的動作,但是玩具鴨不能吃,這個方法就違反了里氏轉換原則。解決方法是可以使用介面,這樣所有的鴨繼承Duck,有吃的動作的鴨繼承介面IDuckEat,玩具鴨直接繼承Duck即可。
介面隔離原則客戶端不應該依賴它不需要的介面。一個類對另一個類的依賴應該建立在最小的介面上。說白了,一個通用的龐大介面並不一定是好事,將行為細分,多個不同的介面反而更容易保證介面隔離。
例如集合都繼承了IEnumerable或者它的泛型,但是並不是所有的方法都整合在了這個介面中,一些不通用的方法我們可以放到其他子介面中,這樣選擇性的繼承父介面或者子介面來保證介面隔離。例如很多集合都可以Insert和RemoveAt,但是Stack和Queue不能隨便插入刪除的,這時Insert和RemoveAt定義在了IEnumerable的子類ICollection的子類IList中,這時如果還存在其他集合可以隨便插入刪除的繼承自IList,而Stack和Queue繼承自ICollection。
ICollection泛型沒有Insert和RemoveAt
Insert和RemoveAt定義在了IList中
依賴倒轉原則將抽象的類定義為一組介面,具體的實現以來這些介面。即高層不依賴於底層,他們都依賴的是那一組介面,即抽象。
例如我們有一臺計算機,計算機有顯示器,鍵盤等,那我們將所有的顯示器都繼承自IDisplay介面,所有的鍵盤繼承自IkeyBoard介面,這樣建構函式Computer(IDisplay display, IKeyBoard keyboard)中,不同品牌電腦當有不同的顯示器鍵盤時,傳入他們特定的顯示器鍵盤即可,這就時依賴一組抽象介面,底層的顯示器鍵盤則依賴對應的介面,所以說兩個依賴的都是介面,一組抽象。將具體型別的顯示器鍵盤透過建構函式傳入給一個具體型別的電腦就是建構函式依賴注入。倘若你電腦定義時依賴的是具體的顯示器鍵盤,後期將無法更改顯示器和鍵盤的型別了。
依賴注入對需求的改變,功能的擴充套件等都更加靈活,模組間的耦合程度降低。