軟件工程師需要理解的面向?qū)ο蟪绦蜷_發(fā)原則(ISP、DIP、LoD等)(面向?qū)ο筌浖こ涕_發(fā)分幾個階段)
導(dǎo)讀
本文示例使用C#語言寫的,但是原理適用與一切面向?qū)ο笳Z言。
其他幾個原則在這篇文章里:軟件工程師需要理解的面向?qū)ο蟪绦蜷_發(fā)原則(SRP、OCP、LSP)
1接口隔離原則(ISP)
定義
不應(yīng)該強迫客戶程序依賴并未使用的方法。接口不應(yīng)包含所有的對象行為,接口應(yīng)盡可能的小。這個原則用來處理“胖”接口所存在的缺點。
為什么要遵循此原則?
如果程序中的一部分更改會影響到程序中完全和它無關(guān)的其他部分,那么更改的代價和影響就變得不可預(yù)測。
違反原則的情形
接口污染,即接口被一個它不總是需要的方法污染,也就是說不是此接口的每一個派生類都需要那個方法。但由于接口已經(jīng)定義了這個方法,那么不需要它的派生類也要實現(xiàn)這個方法。
運用的方式方法
1)使用委托分離接口
對象的客戶端不通過該對象的接口去訪問它,而是通過委托去訪問他。此方案的缺點:委托處理會導(dǎo)致一些很小但仍然存在的運行時間和內(nèi)存的開銷。
2)使用多重繼承分離接口:通常這種做法是首選的。
運用與辨析
在web應(yīng)用開發(fā)中使用倉儲模式來封裝對底層數(shù)據(jù)庫的訪問,為此創(chuàng)建IRepository<T>接口:
public interface IRepository<T> { T GetById(int id); bool Delete(T entity); bool Save(T entity); void Update(T entity); IList<T> Get(string condition); …… }
這是一個典型的胖接口,并不是每一個子類都會實現(xiàn)這么多的方法。對于繼承了這個接口卻不需要實現(xiàn)其中某些方法的接口,只能將方法體設(shè)置為空實現(xiàn)或拋出異常。例如下面的類中不需要實現(xiàn)Get方法,所以在方法體中拋出了異常
public class MRepository<T> : IRepository<T> { public T GetById(int id) { //具體實現(xiàn) } public bool Delete(T entity) { //具體實現(xiàn) } public bool Save(T entity) { //具體實現(xiàn) } public void Update(T entity) { //具體實現(xiàn) } //不需要實現(xiàn)此方法 public IList<T> Get(string condition) { throw new NotImplementedException(); } }
在接口的實現(xiàn)里拋出異常,這樣做顯然違背了里氏替換原則(LSP),解決的辦法是將IRepository<T>拆分成兩個以上的更小的接口,按需實現(xiàn)接口,修改如下:
public interface IRepository<T> { T GetById(int id); bool Delete(T entity); bool Save(T entity); void Update(T entity); } public interface IRepositoryAL<T> { IList<T> Get(string condition); }public class MRepository<T> : IRepository<T> { public T GetById(int id) { //具體實現(xiàn) } public bool Delete(T entity) { //具體實現(xiàn) } public bool Save(T entity) { //具體實現(xiàn) } public void Update(T entity) { //具體實現(xiàn) } }
2依賴倒置原則(DIP)
定義
高層模塊不應(yīng)依賴于低層模塊。二者都應(yīng)依賴于抽象。抽象不應(yīng)該依賴于細節(jié),細節(jié)應(yīng)該依賴于抽象。這樣高層組件與低層組件之間通過抽象的接口來交換而不是具體類。該原則是框架設(shè)計的核心。
為什么要遵守此原則?
如果高層模塊依賴于低層模塊,那么對低層模塊的改動會直接影響到高層模塊,從而迫使他們一次做出改動。
違反原則的情形
高低層組件通過具體類來實現(xiàn)交互。
運用的方式方法
“倒置”不僅僅是依賴關(guān)系的倒置,也是接口所有權(quán)的倒置。當(dāng)使用DIP原則時,往往客戶擁有抽象接口,而他們的服務(wù)者則從這些抽象接口派生。
啟發(fā)式的方法:
1)找到那些指向具體類的引用的變量。
2)找到任何派生自具體類的類。
3)找到那些重寫方法,而基類方法已經(jīng)實現(xiàn)過了。
運用與辨析
依賴倒置式控制反轉(zhuǎn)的精髓,通過控制反轉(zhuǎn)可以深刻的體會到依賴倒置的作用。
3迪米特法則(LoD,又名最少知道原則)
定義
一個對象應(yīng)當(dāng)對其他對象有盡可能少的了解,只和自己關(guān)系最密切對象直接作用。
關(guān)系最密切的對象含義是:
當(dāng)前對象本身,通過該對象方法參數(shù)傳入的對象,此類的其他實例化對象,以及其所在聚集類的其他成員。
為什么要遵守此原則?
降低耦合,減少依賴。
違反原則的情形
和除了上述關(guān)系最密切的對象之間通信。
運用的方式方法
1)限制類及其成員的訪問權(quán)限。
2)引入門面模式和中介者模式。
4組合/聚合復(fù)用原則(CARP)
定義
將已有的多個對象組成一個新對象,達到復(fù)用的目的。
為什么要遵守此原則?
在建模的過程中,我們會發(fā)現(xiàn),某些實體之間不具有繼承關(guān)系,但是他們之間卻有一些像是的操作,為了實現(xiàn)這種無法用繼承表達的關(guān)系,我們遵照CARP原則。
5DRY原則(不要重復(fù)自己)
避免重復(fù)相同或相似的代碼。
運用與辨析
定義攔截器或過濾器充分體現(xiàn)了DRY原則。
例如使用ASP.NET MVC創(chuàng)建企業(yè)級應(yīng)用的過程中,定義了如下的控制器:
public class ExcludedDataController : BaseController{[HttpPost] public ActionResult Add(ExcludedDataInfo info) { if (Request.IsAjaxRequest()) { //其他代碼 } return new EmptyResult(); }public ActionResult Del(ExcludedDataInfo info) { if (Request.IsAjaxRequest()) { //其他代碼 } return new EmptyResult(); }public ActionResult BatchAdd(string itemCodes, int currentNavId, int library_DataBase_ID) { if (Request.IsAjaxRequest()) { //其他代碼 } return new EmptyResult(); }}
其中三個方法中都調(diào)用了Request.IsAjaxRequest()方法,明顯違反了DRY原則,解決的辦法是可以在控制器上添加攔截器。但是或許此控制器的操作中還有不被Ajax調(diào)用的操作,那么可以將這些操作移除,放入一個新的控制器中。
6控制反轉(zhuǎn)(IoC)
控制反轉(zhuǎn)是基于面向?qū)ο蟮脑瓌t,提倡松耦合理念的設(shè)計原則,允許獨立開發(fā)應(yīng)用程序的各個組件。
實現(xiàn)方式
實現(xiàn)方式有兩種:依賴注入,服務(wù)定位。
依賴注入:引用其他的dll,組件之間的引用,一個類持有另一個類,這些都可以被看做是依賴。最常遇到的是一個類持有另一個類的問題。
依賴注入有三種方式:構(gòu)造函數(shù)注入,屬性注入,方法注入。最常使用的是構(gòu)造函數(shù)的注入。
服務(wù)定位:通過IoC容器獲取依賴的具體類型,并將其賦給接口。
運用與辨析
記錄Entity Framework執(zhí)行SQL語句對優(yōu)化系統(tǒng)有極大的幫助。為記錄SQL定擴展命令攔截器IDbCommandInterceptor,在實現(xiàn)的方法中記錄SQL??梢詫QL記錄到本地文本文件中,也可以將SQL存儲到數(shù)據(jù)庫中,實現(xiàn)如下:
public class CommandInterceptor : IDbCommandInterceptor { private Logger logger = new Logger(); public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { this.logger.Log<int>(command, interceptionContext); } //其他方法…… }
上面的實現(xiàn)包含了一個依賴項,即Logger,如果后續(xù)改變存儲SQL的媒介,那么就要修改Logger.Log這個方法,明顯違反了OCP原則,也沒有遵循DIP原則。所以將其更改如下:
public class CommandInterceptor : IDbCommandInterceptor { private ICommandLogger logger; public CommandInterceptor(ICommandLogger logger) { this.logger = logger; } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { this.logger.Log<int>(command, interceptionContext); } //其他代碼 }