網(wǎng)站首頁 編程語言 正文
一、前言
我們先來看看傳統(tǒng)的三層架構(gòu),如下圖所示:
從上圖中我們可以看到:在傳統(tǒng)的三層架構(gòu)中,層與層之間是相互依賴的,UI層依賴于BLL層,BLL層依賴于DAL層。分層的目的是為了實現(xiàn)“高內(nèi)聚、低耦合”。傳統(tǒng)的三層架構(gòu)只有高內(nèi)聚沒有低耦合,層與層之間是一種強依賴的關(guān)系,這也是傳統(tǒng)三層架構(gòu)的一種缺點。這種自上而下的依賴關(guān)系會導(dǎo)致級聯(lián)修改,如果低層發(fā)生變化,可能上面所有的層都需要去修改,而且這種傳統(tǒng)的三層架構(gòu)也很難實現(xiàn)團(tuán)隊的協(xié)同開發(fā),因為上層功能取決于下層功能的實現(xiàn),下面功能如果沒有開發(fā)完成,則上層功能也無法進(jìn)行。
傳統(tǒng)的三層架構(gòu)沒有遵循依賴倒置原則(DIP)來設(shè)計,所以就會出現(xiàn)上面的問題。
二、依賴倒置
依賴倒置(DIP):Dependence Inversion Principle的縮寫,主要有兩層含義:
- 高層次的模塊不應(yīng)該依賴低層次的模塊,兩者都應(yīng)該依賴其抽象。
- 抽象不應(yīng)該依賴于具體,具體應(yīng)該依賴于抽象。
我們先來解釋第一句話:高層模塊不應(yīng)該直接依賴低層模塊的具體實現(xiàn),而是應(yīng)該依賴于低層模塊的抽象,也就是說,模塊之間的依賴是通過抽象發(fā)生的,實現(xiàn)類之間不應(yīng)該發(fā)生直接的依賴關(guān)系,他們的依賴關(guān)系應(yīng)該通過接口或者抽象類產(chǎn)生。
在來解釋第二句話:接口或者抽象類不應(yīng)該依賴于實現(xiàn)類。舉個例子,假如我們要寫B(tài)LL層的代碼,直接就去實現(xiàn)了功能,等到開發(fā)完成以后發(fā)現(xiàn)沒有使用依賴倒置原則,這時候在根據(jù)實現(xiàn)類去寫接口,這種是不對的,應(yīng)該首先設(shè)計抽象,然后在根據(jù)抽象去實現(xiàn),應(yīng)該要面向接口編程。
我們在上面說過,在傳統(tǒng)的三層架構(gòu)里面沒有使用依賴倒置原則,那么把依賴倒置原則應(yīng)用到傳統(tǒng)的三層架構(gòu)里面會如何呢?我們知道,在傳統(tǒng)的三層架構(gòu)里面,UI層直接依賴于BLL層,BLL層直接依賴于DAL層,由于每一層都是依賴下一層的實現(xiàn),所以說當(dāng)下層發(fā)生變化的時候,它的上一層也要發(fā)生變化,這時候可以根據(jù)依賴倒置原則來重新設(shè)計三層架構(gòu)。
UI、BLL、DAL三層之間應(yīng)該沒有直接的依賴關(guān)系,都應(yīng)該依賴于接口。首先應(yīng)該先確定出接口,DAL層抽象出IDAL接口,BLL層抽象出IBLL接口,這樣UI層依賴于IBLL接口,BLL實現(xiàn)IBLL接口。BLL層依賴于IDAL接口,DAL實現(xiàn)IDAL接口。如下圖所示:
我們上面講了依賴倒置原則,那么依賴倒置原則的目的是什么呢?
有了依賴倒置原則,可以使我們的架構(gòu)更加的穩(wěn)定、靈活,也能更好地應(yīng)對需求的變化。相對于細(xì)節(jié)的多變性,抽象的東西是穩(wěn)定的。所以以抽象為基礎(chǔ)搭建起來的架構(gòu)要比以細(xì)節(jié)為基礎(chǔ)搭建起來的架構(gòu)要穩(wěn)定的多。
在傳統(tǒng)的三層架構(gòu)里面,僅僅增加一個接口層,我們就實現(xiàn)了依賴倒置,目的就是降低層與層之間的耦合。有了這樣的接口層,三層架構(gòu)才真正實現(xiàn)了“高內(nèi)聚、低耦合”的思想。
依賴倒置原則是架構(gòu)層面上的,那么如何在代碼層面上實現(xiàn)呢?下面看控制反轉(zhuǎn)。
三、控制反轉(zhuǎn)
控制反轉(zhuǎn)(IOC):Inversion of Control的縮寫,一種反轉(zhuǎn)流、依賴和接口的方式,它把傳統(tǒng)上由程序代碼直接操控的對象的控制器(創(chuàng)建、維護(hù))交給第三方,通過第三方(IOC容器)來實現(xiàn)對象組件的裝配和管理。
IOC容器,也可以叫依賴注入框架,是由一種依賴注入框架提供的,主要用來映射依賴,管理對象的創(chuàng)建和生存周期。IOC容器本質(zhì)上就是一個對象,通常會把程序里面所有的類都注冊進(jìn)去,使用這個類的時候,直接從容器里面去解析。
四、依賴注入
依賴注入(DI):Dependency Injection的縮寫。依賴注入是控制反轉(zhuǎn)的一種實現(xiàn)方式,依賴注入的目的就是為了實現(xiàn)控制反轉(zhuǎn)。
依賴注入是一種工具或手段,目的是幫助我們開發(fā)出松耦合、可維護(hù)的程序。
依賴注入常用的方式有以下幾種:
- 構(gòu)造函數(shù)注入。
- 屬性注入。
- 方法注入。
其中構(gòu)造函數(shù)注入是使用最多的,其次是屬性注入。
看下面的一個例子:父親給孩子講故事,只要給這個父親一本書,他就可以照著這本書給孩子講故事。我們下面先用最傳統(tǒng)的方式實現(xiàn)一下,這里不使用任何的設(shè)計原則和設(shè)計模式。
首先定義一個Book類:
namespace DipDemo1 { public class Book { public string GetContent() { return "從前有座山,山上有座廟....."; } } }
然后在定義一個Father類:
using System; namespace DipDemo1 { public class Father { public void Read() { Book book = new Book(); Console.WriteLine("爸爸開始給孩子講故事了"); 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(); } } }
我們來看看關(guān)系圖:
我們看到:Father是直接依賴于Book類。
這時需求發(fā)生了變化,不給爸爸書了,給爸爸報紙,讓爸爸照著報紙給孩子讀報紙,這時該怎么做呢?按照傳統(tǒng)的方式,我們這時候需要在定義一個報紙類:
namespace DipDemo1 { public class NewsPaper { public string GetContent() { return "新聞"; } } }
這時依賴關(guān)系變了,因為爸爸要依賴于報紙了,這就導(dǎo)致還要修改Father類:
using System; namespace DipDemo1 { public class Father { public void Read() { // 讀書 // Book book = new Book(); //Console.WriteLine("爸爸開始給孩子講故事了"); //Console.WriteLine(book.GetContent()); // 報紙 NewsPaper paper = new NewsPaper(); Console.WriteLine("爸爸開始給孩子講新聞"); Console.WriteLine(paper.GetContent()); } } }
假設(shè)后面需求又變了,又不給報紙了,換成雜志、平板電腦等。需求在不斷的變化,不管怎么變化,對于爸爸來說,他一直在讀讀物,但是具體讀什么讀物是會發(fā)生變化,這就是細(xì)節(jié),也就是說細(xì)節(jié)會發(fā)生變化。但是抽象是不會變的。如果這時候還是使用傳統(tǒng)的OOP思想來解決問題,那么會導(dǎo)致程序不斷的在修改。下面使用工廠模式來優(yōu)化:
首先創(chuàng)建一個接口:
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 "王聰聰被限制高消費......"; } } }
然后創(chuàng)建一個工廠類:
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; } } } }
里面方法的返回值是一個接口類型。最后在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("爸爸開始給孩子講故事了"); 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(); } } }
我們這時候可以在看看依賴關(guān)系圖:
這時Father已經(jīng)和Book、Paper沒有任何依賴了,F(xiàn)ather依賴于IReader接口,還依賴于工廠類,而工廠類又依賴于Book和Paper類。這里實際上已經(jīng)實現(xiàn)了控制反轉(zhuǎn)。Father(高層)不依賴于低層(Book、Paper)而是依賴于抽象(IReader),而且具體的實現(xiàn)也不是由高層來創(chuàng)建,而是由第三方來創(chuàng)建(這里是工廠類)。但是這里只是使用工廠模式來模擬控制反轉(zhuǎn),而沒有實現(xiàn)依賴的注入,依賴還是需要向工廠去請求。
下面繼續(xù)優(yōu)化代碼,這里只需要修改Father類:
using System; namespace DipDemo3 { public class Father { public IReader Reader { get; set; } ////// 構(gòu)造函數(shù)的參數(shù)是IReader接口類型 /// /// public Father(IReader reader) { Reader = reader; } public void Read() { Console.WriteLine("爸爸開始給孩子講故事了"); 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(); } } }
由于這里沒有了工廠,我們還是需要在代碼里面實例化具體的實現(xiàn)類。如果有一個IOC容器,我們就不需要自己new一個實例了,而是由容器幫我們創(chuàng)建實例,創(chuàng)建完成以后在把依賴對象注入進(jìn)去。
我們在來看一下依賴關(guān)系圖:
下面我們使用Unity容器來繼續(xù)優(yōu)化上面的代碼,首先需要在項目里面安裝Unity,直接在NuGet里面搜索即可:
這里只需要修改Main方法調(diào)用即可:
using System; using Unity; namespace UnityDemo { class Program { static void Main(string[] args) { // 創(chuàng)建容器 var container = new UnityContainer(); // 掃描程序集、配置文件 // 在容器里面注冊接口和實現(xiàn)類,創(chuàng)建依賴關(guān)系 container.RegisterType(); // 在容器里面注冊Father container.RegisterType (); // 從容器里拿出要使用的類,容器會自行創(chuàng)建father對 // 還會從容器里去拿到他所依賴的對象,并且注入進(jìn)來 // var father = container.Resolve (); // 調(diào)用方法 father.Read(); Console.ReadKey(); } } }
原文鏈接:https://www.cnblogs.com/dotnet261010/p/12289609.html
相關(guān)推薦
- 2023-04-06 關(guān)于yolov8訓(xùn)練的一些改動及注意事項_python
- 2023-07-16 springboot動態(tài)端口
- 2022-05-13 (Qt)使用QCommandLineParser進(jìn)行程序的命令行解析
- 2022-07-16 不同存圖方式下的DFS和BFS實現(xiàn)
- 2022-04-25 基于NPOI用C#開發(fā)的Excel以及表格設(shè)置_C#教程
- 2022-05-10 react設(shè)置多個classname
- 2022-09-26 C++繼承關(guān)系下的構(gòu)造與析構(gòu)
- 2022-10-11 CFS調(diào)度算法調(diào)度時機的理解
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支