這是理解SOLID原則中,關于依賴倒置原則如何幫助我們編寫低耦合和可測試代碼的第一篇文章。寫在前頭
當我們在讀書,或者在和一些別的開發(fā)者聊天的時候,可能會談及或者聽到術語SOILD。在這些討論中,一些人會提及它的重要性,以及一個理想中的系統(tǒng),應當包含它所包含的5條原則的特性。
我們在每次的工作中,你可能沒有那么多時間思考關于架構這個比較大的概念,或者在有限的時間內(nèi)或督促下,你也沒有辦法實踐一些好的設計理念。
但是,這些原則存在的意義不是讓我們“跳過”它們。軟件工程師應當將這些原則應用到他們的開發(fā)工作中。所以,在你每一次敲代碼的時候,如何能夠正確的將這些原則付諸于行,才是真正的問題所在。如果可以那樣的話,你的代碼會變得更優(yōu)雅。
SOLID原則是由5個基本的原則構成的。這些概念會幫助創(chuàng)造更好(或者說更健壯)的軟件架構。這些原則包含(SOLID是這5個原則的開頭字母組成的縮略詞):
S stands for SRP (Single responsibility principle):單一職能原則
O stands for OCP (Open closed principle):開閉原則
L stands for LSP (Liskov substitution principle):里氏替換原則
I stand for ISP ( Interface segregation principle):接口隔離原則
D stands for DIP ( Dependency inversion principle):依賴倒置原則
起初這些原則是Robert C. Martin在1990年提出的,遵循這些原則可以幫助我們更好的構建,低耦合、高內(nèi)聚的軟件架構,同時能夠真正的對現(xiàn)實中的業(yè)務邏輯進行恰到好處的封裝。
不過這些原則并不會使一個差勁的程序員轉變?yōu)橐粋€優(yōu)秀的程序員。這些法則取決于你如何應用它們,如果你是很隨意的應用它們,那等同于你并沒有使用它們一樣。
關于原則和模式的知識能夠幫助你決定在何時何地正確的使用它們。盡管這些原則僅僅是啟示性的,它們是常見問題的常規(guī)解決方案。實踐中,這些原則的正確性已經(jīng)被證實了很多次,所以它們應當成為一種常識。
依賴倒置原則是什么高級模塊不應當依賴于低級模塊。它們都應當依賴于抽象。
抽象不應當依賴于實現(xiàn),實現(xiàn)應當依賴于抽象。
這兩句話的意思是什么呢?
一方面,你會抽象一些東西。在軟件工程和計算機科學中,抽象是一種關于規(guī)劃計算機系統(tǒng)中的復雜性的技術。它的工作原理一般是在一個人與系統(tǒng)交互的復雜環(huán)境中,隱藏當前級別下的更復雜的實現(xiàn)細節(jié),同時它的范圍很廣,常常會覆蓋多個子系統(tǒng)。這樣,當我們在與一個以高級層面作為抽象的系統(tǒng)協(xié)作時,我們僅僅需要在意,我們能做什么,而不是我們如何做。
另外,你會針對你的抽象,有一寫低級別的模塊或者具體實現(xiàn)邏輯。這些東西與抽象是相反的。它們是被用于解決某些特定問題所編寫的代碼。它們的作用域僅僅在某個單元和子系統(tǒng)中。比如,建立一個與MySQL數(shù)據(jù)庫的連接就是一個低級別的實現(xiàn)邏輯,因為它與某個特定的技術領域所綁定。
現(xiàn)在仔細讀這兩句話,我們能夠得到什么暗示呢?
依賴倒置原則存在的真正意義是指,我們需要將一些對象解耦,它們的耦合關系需要達到當一個對象依賴的對象作出改變時,對象本身不需要更改任何代碼。
這樣的架構可以實現(xiàn)一種松耦合的狀態(tài)的系統(tǒng),因為系統(tǒng)中所有的組件,彼此之間都了解很少或者不需要了解系統(tǒng)中其余組件的具體定義和實現(xiàn)細節(jié)。它同時實現(xiàn)了一種可測試和可替換的系統(tǒng)架構,因為在松耦合的系統(tǒng)中,任何組件都可以被提供相同服務的組件所替換。
但是相反的,依賴倒置也有一些缺點,就是你需要一個用于處理依賴倒置邏輯的容器,同時,你還需要配置它。容器通常需要具備能夠在系統(tǒng)中注入服務,這些服務需要具備正確的作用域和參數(shù),還應當被注入正確的執(zhí)行上下文中。
以提供Websocket連接服務為例子舉個例子,我們可以在這個例子中學到更多關于依賴倒置的知識,我們將使用Inversify.js作為依賴倒置的容器,通過這個依賴倒置容器,我們可以看看如何針對提供Websocket連接服務的業(yè)務場景,提供服務。
比如,我們有一個web服務器提供WebSockets連接服務,同時客戶端想要連接服務器,同時接受更新的通知。當前我們有若干種解決方案來提供一個WebSocket服務,比如說Socket.io、Socks或者使用瀏覽器提供的關于原生的WebSocket接口。每一套解決方案,都提供不同的接口和方法供我們調(diào)用,那么問題來了,我們是否可以在一個接口中,將所有的解決方案都抽象成一個提供WebSocket連接服務的提供者?這樣,我們就可以根據(jù)我們的實際需求,使用不同的WebSocket服務提供者。
首先,我們來定義我們的接口:
export interface WebSocketConfiguration { uri: string; options?: Object; } export interface SocketFactory { createSocket(configuration: WebSocketConfiguration): any; }
注意在接口中,我們沒有提供任何的實現(xiàn)細節(jié),因此它既是我們所擁有的抽象。
接下來,如果我們想要一個提供Socket.io服務工廠:
import {Manager} from "socket.io-client"; class SocketIOFactory implements SocketFactory { createSocket(configuration: WebSocketConfiguration): any { return new Manager(configuration.uri, configuration.opts); } }
這里已經(jīng)包含了一些具體的實現(xiàn)細節(jié),因此它不再是抽象,因為它聲明了一個從Socket.io庫中導入的Manager對象,它是我們的具體實現(xiàn)細節(jié)。
我們可以通過實現(xiàn)SocketFactory接口,來增加若干工廠類,只要我們實現(xiàn)這個接口即可。
我們在提供一個關于客戶端連接實例的抽象:
export interface SocketClient { connect(configuration: WebSocketConfiguration): Promise; close(): Promise ; emit(event: string, ...args: any[]): Promise ; on(event: string, fn: Function): Promise ; }
然后再提供一些實現(xiàn)細節(jié):
class WebSocketClient implements SocketClient { private socketFactory: SocketFactory; private socket: any; public constructor(webSocketFactory: SocketFactory) { this.socketFactory = webSocketFactory; } public connect(config: WebSocketConfiguration): Promise{ if (!this.socket) { this.socket = this.socketFactory.createSocket(config); } return new Promise ((resolve, reject) => { this.socket.on("connect", () => resolve()); this.socket.on("connect_error", (error: Error) => reject(error)); }); } public emit(event: string, ...args: any[]): Promise { return new Promise ((resolve, reject) => { if (!this.socket) { return reject("No socket connection."); } return this.socket.emit(event, args, (response: any) => { if (response.error) { return reject(response.error); } return resolve(); }); }); } public on(event: string, fn: Function): Promise { return new Promise ((resolve, reject) => { if (!this.socket) { return reject("No socket connection."); } this.socket.on(event, fn); resolve(); }); } public close(): Promise { return new Promise ((resolve) => { this.socket.close(() => { this.socket = null; resolve(); }); }); } }
值得注意的是,這里我們在構造函數(shù)中,傳入了一個類型是SocketFactory的參數(shù),這是為了滿足關于依賴倒置原則的第一條規(guī)則。對于第二條規(guī)則,我們需要一種方式來提供這個不需要了解內(nèi)部實現(xiàn)細節(jié)的、可替換的、易于配置的參數(shù)。
這也是為什么我們要使用Inversify這個庫的原因,我們來加入一些額外的代碼和注解(裝飾器):
import {injectable} from "inversify"; const webSocketFactoryType: symbol = Symbol("WebSocketFactory"); const webSocketClientType: symbol = Symbol("WebSocketClient"); let TYPES: any = { WebSocketFactory: webSocketFactoryType, WebSocketClient: webSocketClientType }; @injectable() class SocketIOFactory implements SocketFactory {...} ... @injectable() class WebSocketClient implements SocketClient { public constructor(@inject(TYPES.WebSocketFactory) webSocketFactory: SocketFactory) { this.socketFactory = webSocketFactory; }
這些注釋(裝飾器)僅僅會在代碼運行時,在如何提供這些組件實例時,提供一些元數(shù)據(jù),接下來我們僅僅需要創(chuàng)建一個依賴倒置容器,并將所有的對象按正確的類型綁定起來,如下:
import {Container} from "inversify"; import "reflect-metadata"; import {TYPES, SocketClient, SocketFactory, SocketIOFactory, WebSocketClient} from "@web/app"; const provider = new Container({defaultScope: "Singleton"}); // Bindings provider.bind(TYPES.WebSocketClient).to(WebSocketClient); provider.bind (TYPES.WebSocketFactory).to(SocketIOFactory); export default provider;
讓我們來看看我們?nèi)绾问褂梦覀兲峁┻B接服務的客戶端實例:
var socketClient = provider.get(TYPES.WebSocketClient);
當然,使用Inversify可以提供一些更簡單易用的綁定,可以通過瀏覽它的網(wǎng)站來了解。
譯者注一般說到依賴倒置原則,往往第一個想到的術語即是依賴注入,這種在各個技術棧都有應用,之后又會馬上想到spring、ng等前后端框架。
我們確實是通過使用這些框架熟知這個概念的,但是如果你仔細想想的話,是否還有其他的一些場景也使用了類似的概念呢?
比如:
一些使用插件和中間件的框架,如express、redux
js中this的動態(tài)綁定
js中的回調(diào)函數(shù)
也許有的人會不同意我的觀點,會說依賴注入一般都是面向類和接口來講的,這確實有一定的道理,但是我認為沒有必要局限在一種固定的模式中去理解依賴倒置,畢竟它是一種思想,一種模式,在js中,所有的東西都是動態(tài)的,函數(shù)是一等公民,是對象,那么把這些與依賴倒置原則聯(lián)系起來,完全也講的通。我們真正關心的是核心問題是如何解耦,把更多的注意力投入的真正的業(yè)務邏輯中去。
歡迎關注公眾號 全棧101,只談技術,不談人生
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92667.html
摘要:什么是里氏替換原則某個對象實例的子類實例應當可以在不影響程序正確性的基礎上替換它們。除了在編程語言層面,在前端實際工作中,你可能會聽到一個叫作的概念,這個概念我認為也是里氏替換原則的一直延伸。 這是理解SOLID原則,關于里氏替換原則為什么提倡我們面向抽象層編程而不是具體實現(xiàn)層,以及為什么這樣可以使代碼更具維護性和復用性。 什么是里氏替換原則 Objects should be rep...
摘要:事件驅動模型對于一些復雜的事件驅動模型,比如拖拽,往往使用開閉原則會達到意想不到的效果。 這是理解SOLID原則,介紹什么是開閉原則以及它為什么能夠在對已有的軟件系統(tǒng)或者模塊提供新功能時,避免不必要的更改(重復勞動)。 開閉原則是什么 Software entities (classes, modules, functions, etc.) should be open for ext...
摘要:同時,在方法命名上也投入一精力,盡可能地使方法名保持簡單,它將幫助你在重構代碼時,更好的達到單一職責。 這是理解SOLID原則中,關于單一職責原則如何幫助我們編寫低耦合和高內(nèi)聚的第二篇文章。 單一職責原則是什么 之前的第一篇文章闡述了依賴倒置原則(DIP)能夠使我們編寫的代碼變得低耦合,同時具有很好的可測試性,接下來我們來簡單了解下單一職責原則的基本概念: Every module o...
摘要:前言本章我們要講解的是五大原則語言實現(xiàn)的第篇,依賴倒置原則。當應用依賴倒置原則的時候,關系就反過來了。在當靜態(tài)類型語言的上下文里討論依賴倒置原則的時候,耦合的概念包括語義和物理兩種。依賴倒置原則和依賴注入都是關注依賴,并且都是用于反轉。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現(xiàn)的第5篇,依賴倒置原則LSP(The Dependency Invers...
摘要:在改變存儲系統(tǒng)的情況下,必須對進行修改,違背了開放封閉原則。傳統(tǒng)的依賴痛過倒置就能事代碼變得非常靈活,易于改變 聲明:本文并非博主原創(chuàng),而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味的翻譯,能保證90%的原汁性,另外因為是理解翻譯,肯定會有錯誤的地方,歡迎指正。 歡迎轉載,轉載請注明出處,謝謝! 依賴反轉原則 ...
閱讀 1587·2021-11-22 15:33
閱讀 1728·2021-11-15 18:01
閱讀 664·2021-10-09 09:43
閱讀 2604·2021-09-22 16:03
閱讀 758·2021-09-03 10:28
閱讀 3550·2021-08-11 10:22
閱讀 2718·2019-08-30 15:54
閱讀 1761·2019-08-30 14:21