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