C#編程之依賴倒置原則DIP
一、前言
我們先來(lái)看看傳統(tǒng)的三層架構(gòu),如下圖所示:

從上圖中我們可以看到:在傳統(tǒng)的三層架構(gòu)中,層與層之間是相互依賴的,UI層依賴于BLL層,BLL層依賴于DAL層。分層的目的是為了實(shí)現(xiàn)“高內(nèi)聚、低耦合”。傳統(tǒng)的三層架構(gòu)只有高內(nèi)聚沒(méi)有低耦合,層與層之間是一種強(qiáng)依賴的關(guān)系,這也是傳統(tǒng)三層架構(gòu)的一種缺點(diǎn)。這種自上而下的依賴關(guān)系會(huì)導(dǎo)致級(jí)聯(lián)修改,如果低層發(fā)生變化,可能上面所有的層都需要去修改,而且這種傳統(tǒng)的三層架構(gòu)也很難實(shí)現(xiàn)團(tuán)隊(duì)的協(xié)同開(kāi)發(fā),因?yàn)樯蠈庸δ苋Q于下層功能的實(shí)現(xiàn),下面功能如果沒(méi)有開(kāi)發(fā)完成,則上層功能也無(wú)法進(jìn)行。
傳統(tǒng)的三層架構(gòu)沒(méi)有遵循依賴倒置原則(DIP)來(lái)設(shè)計(jì),所以就會(huì)出現(xiàn)上面的問(wèn)題。
二、依賴倒置
依賴倒置(DIP):Dependence Inversion Principle的縮寫,主要有兩層含義:
- 高層次的模塊不應(yīng)該依賴低層次的模塊,兩者都應(yīng)該依賴其抽象。
- 抽象不應(yīng)該依賴于具體,具體應(yīng)該依賴于抽象。
我們先來(lái)解釋第一句話:高層模塊不應(yīng)該直接依賴低層模塊的具體實(shí)現(xiàn),而是應(yīng)該依賴于低層模塊的抽象,也就是說(shuō),模塊之間的依賴是通過(guò)抽象發(fā)生的,實(shí)現(xiàn)類之間不應(yīng)該發(fā)生直接的依賴關(guān)系,他們的依賴關(guān)系應(yīng)該通過(guò)接口或者抽象類產(chǎn)生。
在來(lái)解釋第二句話:接口或者抽象類不應(yīng)該依賴于實(shí)現(xiàn)類。舉個(gè)例子,假如我們要寫B(tài)LL層的代碼,直接就去實(shí)現(xiàn)了功能,等到開(kāi)發(fā)完成以后發(fā)現(xiàn)沒(méi)有使用依賴倒置原則,這時(shí)候在根據(jù)實(shí)現(xiàn)類去寫接口,這種是不對(duì)的,應(yīng)該首先設(shè)計(jì)抽象,然后在根據(jù)抽象去實(shí)現(xiàn),應(yīng)該要面向接口編程。
我們?cè)谏厦嬲f(shuō)過(guò),在傳統(tǒng)的三層架構(gòu)里面沒(méi)有使用依賴倒置原則,那么把依賴倒置原則應(yīng)用到傳統(tǒng)的三層架構(gòu)里面會(huì)如何呢?我們知道,在傳統(tǒng)的三層架構(gòu)里面,UI層直接依賴于BLL層,BLL層直接依賴于DAL層,由于每一層都是依賴下一層的實(shí)現(xiàn),所以說(shuō)當(dāng)下層發(fā)生變化的時(shí)候,它的上一層也要發(fā)生變化,這時(shí)候可以根據(jù)依賴倒置原則來(lái)重新設(shè)計(jì)三層架構(gòu)。
UI、BLL、DAL三層之間應(yīng)該沒(méi)有直接的依賴關(guān)系,都應(yīng)該依賴于接口。首先應(yīng)該先確定出接口,DAL層抽象出IDAL接口,BLL層抽象出IBLL接口,這樣UI層依賴于IBLL接口,BLL實(shí)現(xiàn)IBLL接口。BLL層依賴于IDAL接口,DAL實(shí)現(xiàn)IDAL接口。如下圖所示:

我們上面講了依賴倒置原則,那么依賴倒置原則的目的是什么呢?
有了依賴倒置原則,可以使我們的架構(gòu)更加的穩(wěn)定、靈活,也能更好地應(yīng)對(duì)需求的變化。相對(duì)于細(xì)節(jié)的多變性,抽象的東西是穩(wěn)定的。所以以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)要比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定的多。
在傳統(tǒng)的三層架構(gòu)里面,僅僅增加一個(gè)接口層,我們就實(shí)現(xiàn)了依賴倒置,目的就是降低層與層之間的耦合。有了這樣的接口層,三層架構(gòu)才真正實(shí)現(xiàn)了“高內(nèi)聚、低耦合”的思想。
依賴倒置原則是架構(gòu)層面上的,那么如何在代碼層面上實(shí)現(xiàn)呢?下面看控制反轉(zhuǎn)。
三、控制反轉(zhuǎn)
控制反轉(zhuǎn)(IOC):Inversion of Control的縮寫,一種反轉(zhuǎn)流、依賴和接口的方式,它把傳統(tǒng)上由程序代碼直接操控的對(duì)象的控制器(創(chuàng)建、維護(hù))交給第三方,通過(guò)第三方(IOC容器)來(lái)實(shí)現(xiàn)對(duì)象組件的裝配和管理。
IOC容器,也可以叫依賴注入框架,是由一種依賴注入框架提供的,主要用來(lái)映射依賴,管理對(duì)象的創(chuàng)建和生存周期。IOC容器本質(zhì)上就是一個(gè)對(duì)象,通常會(huì)把程序里面所有的類都注冊(cè)進(jìn)去,使用這個(gè)類的時(shí)候,直接從容器里面去解析。
四、依賴注入
依賴注入(DI):Dependency Injection的縮寫。依賴注入是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式,依賴注入的目的就是為了實(shí)現(xiàn)控制反轉(zhuǎn)。
依賴注入是一種工具或手段,目的是幫助我們開(kāi)發(fā)出松耦合、可維護(hù)的程序。
依賴注入常用的方式有以下幾種:
- 構(gòu)造函數(shù)注入。
- 屬性注入。
- 方法注入。
其中構(gòu)造函數(shù)注入是使用最多的,其次是屬性注入。
看下面的一個(gè)例子:父親給孩子講故事,只要給這個(gè)父親一本書(shū),他就可以照著這本書(shū)給孩子講故事。我們下面先用最傳統(tǒng)的方式實(shí)現(xiàn)一下,這里不使用任何的設(shè)計(jì)原則和設(shè)計(jì)模式。
首先定義一個(gè)Book類:
namespace DipDemo1
{
public class Book
{
public string GetContent()
{
return "從前有座山,山上有座廟.....";
}
}
}然后在定義一個(gè)Father類:
using System;
namespace DipDemo1
{
public class Father
{
public void Read()
{
Book book = new Book();
Console.WriteLine("爸爸開(kāi)始給孩子講故事了");
Console.WriteLine(book.GetContent());
}
}
}然后在Main方法里面調(diào)用:
using System;
namespace DipDemo1
{
class Program
{
static void Main(string[] args)
{
Father father = new Father();
father.Read();
Console.ReadKey();
}
}
}我們來(lái)看看關(guān)系圖:

我們看到:Father是直接依賴于Book類。
這時(shí)需求發(fā)生了變化,不給爸爸書(shū)了,給爸爸報(bào)紙,讓爸爸照著報(bào)紙給孩子讀報(bào)紙,這時(shí)該怎么做呢?按照傳統(tǒng)的方式,我們這時(shí)候需要在定義一個(gè)報(bào)紙類:
namespace DipDemo1
{
public class NewsPaper
{
public string GetContent()
{
return "新聞";
}
}
}這時(shí)依賴關(guān)系變了,因?yàn)榘职忠蕾囉趫?bào)紙了,這就導(dǎo)致還要修改Father類:
using System;
namespace DipDemo1
{
public class Father
{
public void Read()
{
// 讀書(shū)
// Book book = new Book();
//Console.WriteLine("爸爸開(kāi)始給孩子講故事了");
//Console.WriteLine(book.GetContent());
// 報(bào)紙
NewsPaper paper = new NewsPaper();
Console.WriteLine("爸爸開(kāi)始給孩子講新聞");
Console.WriteLine(paper.GetContent());
}
}
}假設(shè)后面需求又變了,又不給報(bào)紙了,換成雜志、平板電腦等。需求在不斷的變化,不管怎么變化,對(duì)于爸爸來(lái)說(shuō),他一直在讀讀物,但是具體讀什么讀物是會(huì)發(fā)生變化,這就是細(xì)節(jié),也就是說(shuō)細(xì)節(jié)會(huì)發(fā)生變化。但是抽象是不會(huì)變的。如果這時(shí)候還是使用傳統(tǒng)的OOP思想來(lái)解決問(wèn)題,那么會(huì)導(dǎo)致程序不斷的在修改。下面使用工廠模式來(lái)優(yōu)化:
首先創(chuàng)建一個(gè)接口:
namespace DipDemo2
{
public interface IReader
{
string GetContent();
}
}然后讓Book類和NewsPaper類都繼承自IReader接口,Book類
namespace DipDemo2
{
public class Book : IReader
{
public string GetContent()
{
return "從前有座山,山上有座廟.....";
}
}
}NewsPaper類:
namespace DipDemo2
{
public class NewsPaper : IReader
{
public string GetContent()
{
return "王聰聰被限制高消費(fèi)......";
}
}
}然后創(chuàng)建一個(gè)工廠類:
namespace DipDemo2
{
public static class ReaderFactory
{
public static IReader GetReader(string readerType)
{
if (string.IsNullOrEmpty(readerType))
{
return null;
}
switch (readerType)
{
case "NewsPaper":
return new NewsPaper();
case "Book":
return new Book();
default:
return null;
}
}
}
}里面方法的返回值是一個(gè)接口類型。最后在Father類里面調(diào)用工廠類:
using System;
namespace DipDemo2
{
public class Father
{
private IReader Reader { get; set; }
public Father(string readerName)
{
// 這里依賴于抽象
Reader = ReaderFactory.GetReader(readerName);
}
public void Read()
{
Console.WriteLine("爸爸開(kāi)始給孩子講故事了");
Console.WriteLine(Reader.GetContent());
}
}
}最后在Main方法里面調(diào)用:
using System;
namespace DipDemo2
{
class Program
{
static void Main(string[] args)
{
Father father = new Father("Book");
father.Read();
Console.ReadKey();
}
}
}我們這時(shí)候可以在看看依賴關(guān)系圖:

這時(shí)Father已經(jīng)和Book、Paper沒(méi)有任何依賴了,F(xiàn)ather依賴于IReader接口,還依賴于工廠類,而工廠類又依賴于Book和Paper類。這里實(shí)際上已經(jīng)實(shí)現(xiàn)了控制反轉(zhuǎn)。Father(高層)不依賴于低層(Book、Paper)而是依賴于抽象(IReader),而且具體的實(shí)現(xiàn)也不是由高層來(lái)創(chuàng)建,而是由第三方來(lái)創(chuàng)建(這里是工廠類)。但是這里只是使用工廠模式來(lái)模擬控制反轉(zhuǎn),而沒(méi)有實(shí)現(xiàn)依賴的注入,依賴還是需要向工廠去請(qǐng)求。
下面繼續(xù)優(yōu)化代碼,這里只需要修改Father類:
using System;
namespace DipDemo3
{
public class Father
{
public IReader Reader { get; set; }
/// <summary>
/// 構(gòu)造函數(shù)的參數(shù)是IReader接口類型
/// </summary>
/// <param name="reader"></param>
public Father(IReader reader)
{
Reader = reader;
}
public void Read()
{
Console.WriteLine("爸爸開(kāi)始給孩子講故事了");
Console.WriteLine(Reader.GetContent());
}
}
}在Main方法里面調(diào)用:
using System;
namespace DipDemo3
{
class Program
{
static void Main(string[] args)
{
var f = new Father(new Book());
f.Read();
Console.ReadKey();
}
}
}如果以后換成了Paper,需要修改代碼:
using System;
namespace DipDemo3
{
class Program
{
static void Main(string[] args)
{
// Book
//var f = new Father(new Book());
//f.Read();
// Paprer
var f = new Father(new Paper());
f.Read();
Console.ReadKey();
}
}
}由于這里沒(méi)有了工廠,我們還是需要在代碼里面實(shí)例化具體的實(shí)現(xiàn)類。如果有一個(gè)IOC容器,我們就不需要自己new一個(gè)實(shí)例了,而是由容器幫我們創(chuàng)建實(shí)例,創(chuàng)建完成以后在把依賴對(duì)象注入進(jìn)去。
我們?cè)趤?lái)看一下依賴關(guān)系圖:

下面我們使用Unity容器來(lái)繼續(xù)優(yōu)化上面的代碼,首先需要在項(xiàng)目里面安裝Unity,直接在NuGet里面搜索即可:

這里只需要修改Main方法調(diào)用即可:
using System;
using Unity;
namespace UnityDemo
{
class Program
{
static void Main(string[] args)
{
// 創(chuàng)建容器
var container = new UnityContainer();
// 掃描程序集、配置文件
// 在容器里面注冊(cè)接口和實(shí)現(xiàn)類,創(chuàng)建依賴關(guān)系
container.RegisterType<IReader, Book>();
// 在容器里面注冊(cè)Father
container.RegisterType<Father>();
// 從容器里拿出要使用的類,容器會(huì)自行創(chuàng)建father對(duì)
// 還會(huì)從容器里去拿到他所依賴的對(duì)象,并且注入進(jìn)來(lái)
//
var father = container.Resolve<Father>();
// 調(diào)用方法
father.Read();
Console.ReadKey();
}
}
}到此這篇關(guān)于C#編程之依賴倒置原則DIP的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- C#實(shí)現(xiàn)六大設(shè)計(jì)原則之依賴倒置原則
- C#面向?qū)ο笤O(shè)計(jì)原則之組合/聚合復(fù)用原則
- C#面向?qū)ο笤O(shè)計(jì)原則之接口隔離原則
- C#面向?qū)ο笤O(shè)計(jì)原則之里氏替換原則
- C#面向?qū)ο笤O(shè)計(jì)原則之單一職責(zé)原則
- C#面向?qū)ο笤O(shè)計(jì)原則之開(kāi)閉原則
- C#實(shí)現(xiàn)六大設(shè)計(jì)原則之迪米特法則
- C#實(shí)現(xiàn)六大設(shè)計(jì)原則之接口隔離原則
- C#實(shí)現(xiàn)六大設(shè)計(jì)原則之里氏替換原則
- C#實(shí)現(xiàn)六大設(shè)計(jì)原則之單一職責(zé)原則
- 淺談C#六大設(shè)計(jì)原則
相關(guān)文章
C#?Winform實(shí)現(xiàn)復(fù)制文件顯示進(jìn)度
這篇文章主要介紹了C#?Winform實(shí)現(xiàn)復(fù)制文件顯示進(jìn)度,用進(jìn)度條來(lái)顯示復(fù)制情況,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
C#簡(jiǎn)單獲取全屏中鼠標(biāo)焦點(diǎn)位置坐標(biāo)的方法示例
這篇文章主要介紹了C#簡(jiǎn)單獲取全屏中鼠標(biāo)焦點(diǎn)位置坐標(biāo)的方法,涉及C#針對(duì)鼠標(biāo)位置Position屬性的簡(jiǎn)單操作技巧,需要的朋友可以參考下2017-07-07
Unity的AssetPostprocessor之Model函數(shù)使用實(shí)戰(zhàn)
這篇文章主要為大家介紹了Unity的AssetPostprocessor之Model函數(shù)使用實(shí)戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

