發(fā)布于:2021-01-25 15:55:21
0
449
0
這是我的基礎(chǔ)系列的第一部分。
我覺得我們真正需要回到的一個(gè)基本問題是如何使用和理解接口的價(jià)值。
在C#和Java這樣的語言中,接口是非常常見的,它們的使用比5-10年前要普遍得多。
但我們必須捫心自問的一個(gè)問題是,“我們是否正確地使用了它們?““
接口解決了什么問題?
我想讓你花點(diǎn)時(shí)間,弄清楚你目前是如何使用接口的。
我想讓你假裝你不知道什么是接口。
準(zhǔn)備好了嗎?
一個(gè)接口試圖解決的基本問題是將我們?nèi)绾问褂媚硞€(gè)東西與如何實(shí)現(xiàn)它分開。
為什么我們要把使用和實(shí)現(xiàn)分開?
這樣我們就可以編寫代碼來處理各種不同的職責(zé)實(shí)現(xiàn),而不必專門處理每個(gè)實(shí)現(xiàn)。
更簡(jiǎn)單地說,這意味著如果我們有一個(gè)Driver類,它應(yīng)該能夠有一個(gè)方法Drive,可以用來驅(qū)動(dòng)任何汽車、船或其他實(shí)現(xiàn)IDriveable接口的類。
Driver類不必為每一類都提供DriveBoat、DriveCar或DriveX方法,這些類支持驅(qū)動(dòng)所需的相同基本操作。
接口試圖解決一個(gè)非常具體的問題,允許我們根據(jù)對(duì)象的行為而不是如何與對(duì)象交互。
接口是合同
接口允許我們指定特定類滿足其他類可以依賴的特定期望。
如果我們有一個(gè)實(shí)現(xiàn)接口的類,我們可以確定它將支持該接口中定義的所有方法。
乍一看,接口似乎類似于具體的繼承,但有一個(gè)關(guān)鍵的區(qū)別。
具體的繼承說Car是一輛汽車,而接口說Car實(shí)現(xiàn)了可驅(qū)動(dòng)的接口。
當(dāng)類實(shí)現(xiàn)一個(gè)接口時(shí),并不意味著類就是那個(gè)接口。因此,完全描述類功能的接口通常是錯(cuò)誤的。
一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,因?yàn)槊總€(gè)接口只討論該類能夠?qū)崿F(xiàn)的特定契約。
接口總是由多個(gè)類實(shí)現(xiàn)的
你可能會(huì)說“不,它們不是,我這里有一個(gè)類,它有一個(gè)其他類都沒有實(shí)現(xiàn)的接口?!?/span>對(duì)此我說,“你做錯(cuò)了?!?/span>但是,別擔(dān)心,不止你一個(gè)人。我也做錯(cuò)了。我們中的許多人不再正確地使用接口,而是使用它們,因?yàn)槲覀兊挠∠笫?,我們永遠(yuǎn)不應(yīng)該直接使用具體的類。
我們害怕應(yīng)用程序之間的緊密耦合,所以不管是否需要接口,我們都會(huì)為每個(gè)類創(chuàng)建接口。
我之所以說接口總是由多個(gè)類實(shí)現(xiàn),有一些非常好的理由。
還記得我們說過如何設(shè)計(jì)接口來解決特定問題嗎?
在我的示例中,我討論了Driver類不必?fù)碛兴梢则?qū)動(dòng)的每種類的方法,而是應(yīng)該依賴于IDrivable接口,并擁有一個(gè)通用的drive方法來驅(qū)動(dòng)實(shí)現(xiàn)IDrivable的任何東西。
我們大多數(shù)人都接受YAGNI原則,即“你不會(huì)需要它。”如果我們只有一個(gè)Car類,而沒有任何其他類需要由司機(jī)類驅(qū)動(dòng),我們就不需要接口。
在某個(gè)時(shí)候,我們可能會(huì)添加一個(gè)Boat類。只有在那個(gè)時(shí)候,我們才真正有了一個(gè)接口將要解決的問題。直到那個(gè)時(shí)候,添加接口是預(yù)期未來要解決的問題。
如果你認(rèn)為自己擅長預(yù)測(cè)何時(shí)需要接口,我希望你做一個(gè)小練習(xí)。進(jìn)入你的代碼庫,數(shù)一數(shù)你擁有的所有接口。然后數(shù)一數(shù)實(shí)現(xiàn)這些接口的所有類。我敢打賭這個(gè)比率非常接近1:1。
但我該怎么測(cè)試呢?如何使用依賴注入?
這兩個(gè)原因可能是錯(cuò)誤使用接口的最合理的原因。
我為創(chuàng)建一個(gè)接口而感到內(nèi)疚,這樣我就可以模擬一些東西,我也為我的依賴注入框架創(chuàng)建一個(gè)接口而感到內(nèi)疚,但這并不能使它正確。
在這里,我不能簡(jiǎn)單地回答您,并且說我可以在沒有接口的情況下解決您的單元測(cè)試或依賴項(xiàng)注入問題,但是我可以談?wù)劄槭裁次覀儾粦?yīng)該彎曲源代碼來適應(yīng)工具或方法。
我之前談到過單元測(cè)試的目的,其中一個(gè)關(guān)鍵的好處是單元測(cè)試有助于指導(dǎo)您的設(shè)計(jì)。單元測(cè)試可以幫助我們分離應(yīng)用程序,并將類整合到單個(gè)職責(zé)中,這使得嘗試和單元測(cè)試具有多個(gè)依賴項(xiàng)的類非常痛苦。
接口是一種快捷方式,它允許我們擺脫類中的大量依賴關(guān)系。
當(dāng)我們把一個(gè)具體類的引用變成一個(gè)接口引用時(shí),我們?cè)谄垓_系統(tǒng)。我們假裝我們的類是解耦的,因?yàn)樗玫氖且粋€(gè)接口而不是一個(gè)具體的類,從而使編寫單元測(cè)試變得更容易。實(shí)際上,它并沒有解耦,它實(shí)際上更耦合,因?yàn)槲覀兊念愸詈系揭粋€(gè)接口,而這個(gè)接口耦合到一個(gè)類。我們所做的只是添加間接級(jí)別。
依賴注入促進(jìn)了同樣的接口濫用問題。至少它現(xiàn)在在C#和Java中的使用方式是這樣的。創(chuàng)建一個(gè)接口僅僅是為了能夠?qū)⒃摻涌诘奈ㄒ粚?shí)現(xiàn)注入到類中,這會(huì)產(chǎn)生不必要的間接級(jí)別,并且不必要地降低類的性能我們的申請(qǐng)。
別誤會(huì)。依賴注入很好。我會(huì)把細(xì)節(jié)留到另一篇文章中,但我相信依賴注入的真正好處是當(dāng)它用于控制使用哪個(gè)接口實(shí)現(xiàn)時(shí),而不是當(dāng)一個(gè)接口只有一個(gè)實(shí)現(xiàn)時(shí)。
歸根結(jié)底,我無法給你一個(gè)好的答案,即如何在不濫用接口的情況下進(jìn)行單元測(cè)試或使用依賴注入。我認(rèn)為你可以通過選擇拆分類并實(shí)際減少依賴性來減少濫用,而不是簡(jiǎn)單地創(chuàng)建一個(gè)接口并將其注入類中,但你仍然會(huì)遇到問題一輛車有一個(gè)引擎,如果你想單元測(cè)試這輛車,你要么要用真正的引擎,要么想辦法模仿它。
這里的關(guān)鍵問題是接口是語言的一部分,但是單元測(cè)試和依賴注入不是。我們正試圖通過使用技巧使它們與語言相適應(yīng)。技巧是我們創(chuàng)建一個(gè)接口來提供類之間的接縫。問題是我們這樣做稀釋了接口的效力。我們真正需要的是一個(gè)語言支持的接縫,讓我們能夠輕松地替換具體類在運(yùn)行時(shí)的實(shí)現(xiàn)。
作者介紹
熱門博客推薦