C#实践设计模式原则SOLID
理論跟實踐的關系,說遠不遠,說近不近。能不能把理論用到實踐上,還真不好說。
?
通常講到設計模式,一個最通用的原則是SOLID:
S - Single Responsibility Principle,單一責任原則
O - Open Closed Principle,開閉原則
L - Liskov Substitution Principle,里氏替換原則
I - Interface Segregation Principle,接口隔離原則
D - Dependency Inversion Principle,依賴倒置原則
嗯,這就是五大原則。
后來又加入了一個:Law of Demeter,迪米特法則。于是,就變成了六大原則。
?
原則好理解。怎么用在實踐中?
一、單一責任原則
單一責任原則,簡單來說就是一個類或一個模塊,只負責一種或一類職責。
看代碼:
public?interface?IUser {void?AddUser();void?RemoveUser();void?UpdateUser();void?Logger();void?Message(); }根據原則,我們會發現,對于IUser來說,前三個方法:AddUser、RemoveUser、UpdateUser是有意義的,而后兩個Logger和Message作為IUser的一部分功能,是沒有意義的并不符合單一責任原則的。
所以,我們可以把它分解成不同的接口:
public?interface?IUser {void?AddUser();void?RemoveUser();void?UpdateUser(); } public?interface?ILog {void?Logger(); } public?interface?IMessage {void?Message(); }拆分后,我們看到,三個接口各自完成自己的責任,可讀性和可維護性都很好。
?
下面是使用的例子,采用依賴注入來做:
public?class?Log?:?ILog {public?void?Logger(){Console.WriteLine("Logged?Error");} } public?class?Msg?:?IMessage {public?void?Message(){Console.WriteLine("Messaged?Sent");} } class?Class_DI {private?readonly?IUser?_user;private?readonly?ILog?_log;private?readonly?IMessage?_msg;public?Class_DI(IUser?user,?ILog?log,?IMessage?msg){this._user?=?user;this._log?=?log;this._msg?=?msg;}public?void?User(){this._user.AddUser();this._user.RemoveUser();this._user.UpdateUser();}public?void?Log(){this._log.Logger();}public?void?Msg(){this._msg.Message();} } public?static?void?Main() {Class_DI?di?=?new?Class_DI(new?User(),?new?Log(),?new?Msg());di.User();di.Log();di.Msg(); }這樣的代碼,看著就漂亮多了。
二、開閉原則
開閉原則要求類、模塊、函數等實體應該對擴展開放,對修改關閉。
?
我們先來看一段代碼,計算員工的獎金:
public?class?Employee {public?int?Employee_ID;public?string?Name;public?Employee(int?id,?string?name){this.Employee_ID?=?id;this.Name?=?name;}public?decimal?Bonus(decimal?salary){return?salary?*?.2M;} } class?Program {static?void?Main(string[]?args){Employee?emp?=?new?Employee(101,?"WangPlus");Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp.Employee_ID,?emp.Name,?emp.Bonus(10000));} }現在假設,計算獎金的公式做了改動。
要實現這個,我們可能需要對代碼進行修改:
public?class?Employee {public?int?Employee_ID;public?string?Name;public?string?Employee_Type;public?Employee(int?id,?string?name,?string?type){this.Employee_ID?=?id;this.Name?=?name;this.Employee_Type?=?type;}public?decimal?Bonus(decimal?salary){if?(Employee_Type?==?"manager")return?salary?*?.2M;elsereturnsalary?*?.1M;} }顯然,為了實現改動,我們修改了類和方法。
這違背了開閉原則。
?
那我們該怎么做?
我們可以用抽象類來實現 - 當然,實際有很多實現方式,選擇最習慣或自然的方式就成:
public?abstract?class?Employee {public?int?Employee_ID;public?string?Name;public?Employee(int?id,?string?name){this.Employee_ID?=?id;this.Name?=?name;}public?abstract?decimal?Bonus(decimal?salary); }然后,我們再實現最初的功能:
public?class?GeneralEmployee?:?Employee {public?GeneralEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?Bonus(decimal?salary){return?salary?*?.2M;} } class?Program {public?static?void?Main(){Employee?emp?=?new?GeneralEmployee(101,?"WangPlus");Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp.Employee_ID,?emp.Name,?emp.Bonus(10000));} }在這兒使用抽象類的好處是:如果未來需要修改獎金規則,則不需要像前邊例子一樣,修改整個類和方法,因為現在的擴展是開放的。
代碼寫完整了是這樣:
public?abstract?class?Employee {public?int?Employee_ID;public?string?Name;public?Employee(int?id,?string?name){this.Employee_ID?=?id;this.Name?=?name;}public?abstract?decimal?Bonus(decimal?salary); }public?class?GeneralEmployee?:?Employee {public?GeneralEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?Bonus(decimal?salary){return?salary?*?.1M;} } public?class?ManagerEmployee?:?Employee {public?ManagerEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?Bonus(decimal?salary){return?salary?*?.2M;} } class?Program {public?static?void?Main(){Employee?emp?=?new?GeneralEmployee(101,?"WangPlus");Employee?emp1?=?new?ManagerEmployee(102,?"WangPlus1");Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp.Employee_ID,?emp.Name,?emp.Bonus(10000));Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp1.Employee_ID,?emp1.Name,?emp1.Bonus(10000));} }三、里氏替換原則
里氏替換原則,講的是:子類可以擴展父類的功能,但不能改變基類原有的功能。它有四層含義:
子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法;
子類中可以增加自己的特有方法;
當子類重載父類的方法時,方法的前置條件(形參)要比父類的輸入參數更寬松;
當子類實現父類的抽象方法時,方法的后置條件(返回值)要比父類更嚴格。
在前邊開閉原則中,我們的例子里,實際上也遵循了部分里氏替換原則,我們用GeneralEmployee和ManagerEmployee替換了父類Employee。
?
還是拿代碼來說。
假設需求又改了,這回加了一個臨時工,是沒有獎金的。
public?class?TempEmployee?:?Employee {public?TempEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?Bonus(decimal?salary){throw?new?NotImplementedException();} } class?Program {public?static?void?Main(){Employee?emp?=?new?GeneralEmployee(101,?"WangPlus");Employee?emp1?=?new?ManagerEmployee(101,?"WangPlus1");Employee?emp2?=?new?TempEmployee(102,?"WangPlus2");Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp.Employee_ID,?emp.Name,?emp.Bonus(10000));Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp1.Employee_ID,?emp1.Name,?emp1.Bonus(10000));Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Bonus:?{2}",?emp2.Employee_ID,?emp2.Name,?emp2.Bonus(10000));Console.ReadLine();} }顯然,這個方式不符合里氏替原則的第四條,它拋出了一個錯誤。
所以,我們需要繼續修改代碼,并增加兩個接口:
interface?IBonus {decimal?Bonus(decimal?salary); } interface?IEmployee {int?Employee_ID?{?get;?set;?}string?Name?{?get;?set;?}decimal?GetSalary(); } public?abstract?class?Employee?:?IEmployee,?IBonus {public?int?Employee_ID?{?get;?set;?}public?string?Name?{?get;?set;?}public?Employee(int?id,?string?name){this.Employee_ID?=?id;this.Name?=?name;}public?abstract?decimal?GetSalary();public?abstract?decimal?Bonus(decimal?salary); } public?class?GeneralEmployee?:?Employee {public?GeneralEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?GetSalary(){return?10000;}public?override?decimal?Bonus(decimal?salary){return?salary?*?.1M;} } public?class?ManagerEmployee?:?Employee {public?ManagerEmployee(int?id,?string?name)?:?base(id,?name){}public?override?decimal?GetSalary(){return?10000;}public?override?decimal?Bonus(decimal?salary){return?salary?*?.1M;} } public?class?TempEmployee?:?IEmployee {public?int?Employee_ID?{?get;?set;?}public?string?Name?{?get;?set;?}public?TempEmployee(int?id,?string?name){this.Employee_ID?=?id;this.Name?=?name;}public?decimal?GetSalary(){return?5000;} } class?Program {public?static?void?Main(){Employee?emp?=?new?GeneralEmployee(101,?"WangPlus");Employee?emp1?=?new?ManagerEmployee(102,?"WangPlus1");Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Salary:?{2}?Bonus:{3}",?emp.Employee_ID,?emp.Name,?emp.GetSalary(),?emp.Bonus(emp.GetSalary()));Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Salary:?{2}?Bonus:{3}",?emp1.Employee_ID,?emp1.Name,?emp1.GetSalary(),?emp1.Bonus(emp1.GetSalary()));List<IEmployee>?emp_list?=?new?List<IEmployee>();emp_list.Add(new?GeneralEmployee(101,?"WangPlus"));emp_list.Add(new?ManagerEmployee(102,?"WangPlus1"));emp_list.Add(new?TempEmployee(103,?"WangPlus2"));foreach?(var?obj?in?emp_list){Console.WriteLine("Employee?ID:?{0}?Name:?{1}?Salary:?{2}?",?obj.EmpId,?obj.Name,?obj.GetSalary());}} }四、接口隔離原則
接口隔離原則要求客戶不依賴于它不使用的接口和方法;一個類對另一個類的依賴應該建立在最小的接口上。
通常的做法,是把一個臃腫的接口拆分成多個更小的接口,以保證客戶只需要知道與它相關的方法。
這個部分不做代碼演示了,可以去看看上邊單一責任原則里的代碼,也遵循了這個原則。
五、依賴倒置原則
依賴倒置原則要求高層模塊不能依賴于低層模塊,而是兩者都依賴于抽象。另外,抽象不應該依賴于細節,而細節應該依賴于抽象。
看代碼:
public?class?Message {public?void?SendMessage(){Console.WriteLine("Message?Sent");} } public?class?Notification {private?Message?_msg;public?Notification(){_msg?=?new?Message();}public?void?PromotionalNotification(){_msg.SendMessage();} } class?Program {public?static?void?Main(){Notification?notify?=?new?Notification();notify.PromotionalNotification();} }這個代碼中,通知完全依賴Message類,而Message類只能發送一種通知。如果我們需要引入別的類型,例如郵件和SMS,則需要修改Message類。
下面,我們使用依賴倒置原則來完成這段代碼:
public?interface?IMessage {void?SendMessage(); } public?class?Email?:?IMessage {public?void?SendMessage(){Console.WriteLine("Send?Email");} } public?class?SMS?:?IMessage {public?void?SendMessage(){Console.WriteLine("Send?Sms");} } public?class?Notification {private?IMessage?_msg;public?Notification(IMessage?msg){this._msg?=?msg;}public?void?Notify(){_msg.SendMessage();} } class?Program {public?static?void?Main(){Email?email?=?new?Email();Notification?notify?=?new?Notification(email);notify.Notify();SMS?sms?=?new?SMS();notify?=?new?Notification(sms);notify.Notify();} }通過這種方式,我們把代碼之間的耦合降到了最小。
六、迪米特法則
迪米特法則也叫最少知道法則。從稱呼就可以知道,意思是:一個對象應該對其它對象有最少的了解。
在寫代碼的時候,盡可能少暴露自己的接口或方法。寫類的時候,能不public就不public,所有暴露的屬性、接口、方法,都是不得不暴露的,這樣能確保其它類對這個類有最小的了解。
這個原則沒什么需要多講的,調用者只需要知道被調用者公開的方法就好了,至于它內部是怎么實現的或是有其他別的方法,調用者并不關心,調用者只關心它需要用的。反而,如果被調用者暴露太多不需要暴露的屬性或方法,那么就可能導致調用者濫用其中的方法,或是引起一些其他不必要的麻煩。
?
最后說兩句:所謂原則,不是規則,不是硬性的規定。在代碼中,能靈活應用就好,不需要非拘泥于形式,但是,用好了,會讓代碼寫得很順手,很漂亮。
?
(全文完)
喜歡就來個三連,讓更多人因你而受益
總結
以上是生活随笔為你收集整理的C#实践设计模式原则SOLID的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于PaaS平台的多应用自集成方案之公共
- 下一篇: 如何校验内存数据的一致性,Dynamic