六大原則是設計模式的基石, 是后面所提具體的二十三種設計模式的指導思想
總則: 開放封閉原則
對擴展開放, 對修改封閉
當我們需要添加新的功能時, 可以通過添加新的代碼或者模塊來實作, 而不需要修改已有的功能模塊, 這樣可以避免新增的功能影響到原來已經在正常運行的功能
最簡單的例子就是函式多載
public void Add(int i)
{
this.List.Add(i.ToString("N"));
}
public void Add(string i)
{
this.List.Add(i);
}
public void Add(DateTime time)
{
this.List.Add(time.ToString("yyyy-MM-dd HH:mm:ss.fff"));
}
當我們需要Add一個新的型別時, 只需要添加一個多載函式即可, 不需要修改已有的Add函式
以下算是一個反例
public void Add(object obj)
{
if (obj is int i)
this.List.Add(i.ToString("N"));
else if (obj is string str)
this.List.Add(str);
else if (obj is DateTime time)
this.List.Add(time.ToString("yyyy-MM-dd HH:mm:ss.fff"));
}
單一職責原則
一個類或者一個方法只有一個職責, 只做一件事情, 只處理一個業務
該原則要求盡可能降低類或方法的復雜度, 提高可讀性
下面先來一個簡單的反例
public void SetUser(string name, int age, DateTime birthday)
{
this.name = name;
this.age = age;
this.birthday = birthday;
}
假設類中有一個用于設定用戶資訊的SetUser方法, 可以通過傳入三個引數設定三個欄位的值
此時這個函式擁有三個職責, 設定name 設定age 設定birthday
如果我想要單獨設定其中的某一個欄位, 在現在這種情況下我需要先獲取另外兩個欄位的值, 然后一起傳給這個函式
這在使用的時候就非常不方便, 我們可以根據這三個職責將函式進行拆分
public void InitUser(string name, int age, DateTime birthday)
{
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void SetName(string name)
{
this.name = name;
}
public void SetAge(int age)
{
this.age = age;
}
public void SetBirthday(DateTime birthday)
{
this.birthday = birthday;
}
將原來的SetUser改為InitUser, 意為初始化用戶資訊, 這時候才需要傳全部的三個引數
新增了 SetName SetAge SetBirthday三個函式, 分別對應 設定name 設定age 設定birthday這三個職責
里氏替換原則
子類可以替換父類, 并保持父類的功能和特性, 父類可以出現的地方, 都可以使用其子類進行代替
簡單來說就是子類的行為應當盡量與父類保持一致
下面來一個簡單的反面例子
public class User
{
protected string name;
public virtual void SetName(string name)
{
this.name = name;
}
}
public class Student: User
{
public override void SetName(string name)
{
}
}
子類Student重寫了SetName方法, 但是這個重寫的方法并不會對name欄位進行修改, 這無疑就違反了SetName方法的本意, 會讓其他人在使用這個方法時會不小心掉進坑里
public class User
{
protected string name;
public virtual void SetName(string name)
{
this.name = name;
}
}
public class Student: User
{
public override void SetName(string name)
{
this.name = name;
}
}
一般來說正常實作這個方法即可
依賴倒置原則
高層模塊不應該依賴于低層模塊, 二者都應該依賴于抽象, 而不是具體的實作細節
public class Apple
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Pineapple
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Pen
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class PenPineappleApplePen
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class User
{
private List<Apple> Apples { get; set; }
private List<Pineapple> Pineapples { get; set; }
private List<Pen> Pens { get; set; }
private List<PenPineappleApplePen> PenPineappleApplePens { get; set; }
public void AddApple(Apple good)
{
Apples.Add(good);
}
public void AddPineapple(Pineapple good)
{
Pineapples.Add(good);
}
public void AddPen(Pen good)
{
Pens.Add(good);
}
public void AddPenPineappleApplePen(PenPineappleApplePen good)
{
PenPineappleApplePens.Add(good);
}
public decimal Count()
{
decimal sum = 0;
sum += Apples.Sum(item => item.Price);
sum += Pineapples.Sum(item => item.Price);
sum += Pens.Sum(item => item.Price);
sum += PenPineappleApplePens.Sum(item => item.Price);
return sum;
}
}
上面的User可以購買四種商品, 每種商品都可以添加多個, 加完之后還可以結算價格
但每增加一種商品, User類就需要進行一次修改, 這無疑是非常麻煩且違反開閉原則的
所以我們可以簡單地修改一下
public abstract class Good
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Apple: Good
{
}
public class Pineapple: Good
{
}
public class Pen: Good
{
}
public class PenPineappleApplePen: Good
{
}
public class User
{
private List<Good> Goods { get; set; }
public void AddGood(Good good)
{
Goods.Add(good);
}
public decimal Count()
{
decimal sum = Goods.Sum(item => item.Price);
return sum;
}
}
讓User不再依賴具體的商品, 而是依賴抽象的Good, 這樣即使具體的商品實作部分有增減修改, 只要抽象的Good沒有變化, 就不需要修改現有的模塊
介面隔離原則
盡量使用多個專門的介面, 而不是單一的總介面
有時候我們會設計一個"大而全"的介面, 能做很多事情
public interface ICrud<T>
{
T Get(object id);
T[] GetList(Condition input);
void Add(T input);
void Update(T input);
void Delete(object id);
}
比如以上的ICrud介面就定義了增刪查改總共5個方法, 實作這個介面時需要將這五個方法都實作一遍
但有些情況下我們可能只需要使用查詢功能, 這時候就要額外寫用不到的三個實作, 會使類變得臃腫, 并且可能會產生副作用
此時就可以考慮按照讀寫或者增刪查改拆分介面, 下面就簡單拆分一下
public interface IRead<T>
{
T Get(object id);
T[] GetList(Condition input);
}
public interface IWrite<T>
{
void Add(T input);
void Update(T input);
void Delete(object id);
}
public interface ICrud<T>: IRead<T>, IWrite<T>
{
}
拆分之后的ICrud不受影響, 多出來IRead和IWrite對應讀寫, 在只讀的情況下我們可以只實作IRead
如果有需要, 可以再根據增刪查改進行介面拆分
最少知道原則
一個物件應該對其他物件有盡可能少的了解, 不應該直接與其他物件發生聯系
這樣做的目的是降低類之間的依賴, 降低耦合, 從而使各個功能模塊盡可能獨立
public class Teacher
{
public void GetStudentInfo(Student stu)
{
string info = $"學生名稱: {stu.Name}; 年齡: {stu.Age}";
}
}
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
以上的Teacher在GetStudentInfo中嘗試獲取學生資訊拼接而成的字串, 此時Teacher類對Student類的細節知道的就太多了, 如果Student中添加了新的屬性, Teacher中就需要針對這個新加的屬性做對應修改, 然而這些新加的屬性可能與Teacher本身并沒有什么關聯, 也不是Teacher需要關心的, 這種高耦合設計會在日后帶來很多麻煩
所以我們需要將這些細節屏蔽掉
public class Teacher
{
public void GetStudentInfo(Student stu)
{
string info = stu.GetInfo();
}
}
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
public bool Gender { get; set; }
public string GetInfo()
{
return $"學生名稱: {Name}; 年齡: {Age}; 生日: {Birthday.ToString("yyyy-MM-dd")}; 性別: {(Gender ? "男" : "女" )}";
}
}
像這樣把具體的拼接程序放到Student中去實作, 盡可能讓Student的更改只局限在Student中, 不影響其公開暴露出來的部分, 只要GetInfo這樣的業務沒有發生變化, Teacher就不需要進行修改
合成復用原則
盡量使用合成, 而不是通過繼承達到復用的目的
程式員對于"復用"是有執念的, 一般來說不喜歡把同一個功能寫兩遍, 復用代碼最簡單的方式是 ctrl + c ctrl + v, 這種方式雖然簡單, 但是也有很多隱患, 比如無法統一修改
另一種比較簡單直接的方式是繼承, 繼承一個現有的類即可獲得這個類的功能
public class Gun
{
public string ProductNo { get; set; }
public void Shoot()
{
Console.WriteLine("啪");
}
}
public class Police: Gun
{
}
Police通過繼承Gun可以復用Shoot方法, 但是與此同時也繼承了ProductNo生產批次號, 從業務上來說, Police與生產批次號應該是完全沒有關系的, 又不是機械戰警
所以這里通過繼承來復用就非常不合適了
public class Gun
{
public string ProductNo { get; set; }
public void Shoot()
{
Console.WriteLine("啪");
}
}
public class Police
{
private Gun gun;
public Police(Gun gun)
{
this.gun = gun;
}
public void Shoot()
{
gun.Shoot();
}
}
通過組合進行復用可能是一種更合適的方式
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/547702.html
標籤:設計模式
上一篇:設計模式-index
下一篇:KCP協議淺析
