這是理解
SOLID
原則中,關於
依賴倒置原則如何幫助咱們編寫低耦合和可測試代碼的第一篇文章。
當咱們在讀書,或者在和一些別的開發者聊天的時候,可能會談及或者聽到術語SOILD
。在這些討論中,一些人會說起它的重要性,以及一個理想中的系統,應當包含它所包含的5條原則的特性。程序員
咱們在每次的工做中,你可能沒有那麼多時間思考關於架構這個比較大的概念,或者在有限的時間內或督促下,你也沒有辦法實踐一些好的設計理念。web
可是,這些原則存在的意義不是讓咱們「跳過」它們。軟件工程師應當將這些原則應用到他們的開發工做中。因此,在你每一次敲代碼的時候,如何可以正確的將這些原則付諸於行,纔是真正的問題所在。若是能夠那樣的話,你的代碼會變得更優雅。spring
SOLID
原則是由5個基本的原則構成的。這些概念會幫助創造更好(或者說更健壯)的軟件架構。這些原則包含(SOLID
是這5個原則的開頭字母組成的縮略詞):數據庫
起初這些原則是Robert C. Martin在1990年提出的,遵循這些原則能夠幫助咱們更好的構建,低耦合、高內聚的軟件架構,同時可以真正的對現實中的業務邏輯進行恰到好處的封裝。express
不過這些原則並不會使一個差勁的程序員轉變爲一個優秀的程序員。這些法則取決於你如何應用它們,若是你是很隨意的應用它們,那等同於你並無使用它們同樣。redux
關於原則和模式的知識可以幫助你決定在什麼時候何地正確的使用它們。儘管這些原則僅僅是啓示性的,它們是常見問題的常規解決方案。實踐中,這些原則的正確性已經被證明了不少次,因此它們應當成爲一種常識。segmentfault
這兩句話的意思是什麼呢?後端
一方面,你會抽象一些東西。在軟件工程和計算機科學中,抽象是一種關於規劃計算機系統中的複雜性的技術。它的工做原理通常是在一我的與系統交互的複雜環境中,隱藏當前級別下的更復雜的實現細節,同時它的範圍很廣,經常會覆蓋多個子系統。這樣,當咱們在與一個以高級層面做爲抽象的系統協做時,咱們僅僅須要在乎,咱們能作什麼,而不是咱們如何作。瀏覽器
另外,你會針對你的抽象,有一寫低級別的模塊或者具體實現邏輯。這些東西與抽象是相反的。它們是被用於解決某些特定問題所編寫的代碼。它們的做用域僅僅在某個單元和子系統中。好比,創建一個與MySQL數據庫的鏈接就是一個低級別的實現邏輯,由於它與某個特定的技術領域所綁定。服務器
如今仔細讀這兩句話,咱們可以獲得什麼暗示呢?
依賴倒置原則存在的真正意義是指,咱們須要將一些對象解耦,它們的耦合關係須要達到當一個對象依賴的對象做出改變時,對象自己不須要更改任何代碼。
這樣的架構能夠實現一種鬆耦合的狀態的系統,由於系統中全部的組件,彼此之間都瞭解不多或者不須要了解系統中其他組件的具體定義和實現細節。它同時實現了一種可測試和可替換的系統架構,由於在鬆耦合的系統中,任何組件均可以被提供相同服務的組件所替換。
可是相反的,依賴倒置也有一些缺點,就是你須要一個用於處理依賴倒置邏輯的容器,同時,你還須要配置它。容器一般須要具有可以在系統中注入服務,這些服務須要具有正確的做用域和參數,還應當被注入正確的執行上下文中。
舉個例子,咱們能夠在這個例子中學到更多關於依賴倒置的知識,咱們將使用Inversify.js
做爲依賴倒置的容器,經過這個依賴倒置容器,咱們能夠看看如何針對提供Websocket
鏈接服務的業務場景,提供服務。
好比,咱們有一個web服務器提供WebSockets
鏈接服務,同時客戶端想要鏈接服務器,同時接受更新的通知。當前咱們有若干種解決方案來提供一個WebSocket服務,好比說Socket.io
、Socks
或者使用瀏覽器提供的關於原生的WebSocket
接口。每一套解決方案,都提供不一樣的接口和方法供咱們調用,那麼問題來了,咱們是否能夠在一個接口中,將全部的解決方案都抽象成一個提供WebSocket
鏈接服務的提供者?這樣,咱們就能夠根據咱們的實際需求,使用不一樣的WebSocket服務提供者。
首先,咱們來定義咱們的接口:
export interface WebSocketConfiguration { uri: string; options?: Object; } export interface SocketFactory { createSocket(configuration: WebSocketConfiguration): any; }
注意在接口中,咱們沒有提供任何的實現細節,所以它既是咱們所擁有的抽象。
接下來,若是咱們想要一個提供Socket.io
服務工廠:
import {Manager} from 'socket.io-client'; class SocketIOFactory implements SocketFactory { createSocket(configuration: WebSocketConfiguration): any { return new Manager(configuration.uri, configuration.opts); } }
這裏已經包含了一些具體的實現細節,所以它再也不是抽象,由於它聲明瞭一個從Socket.io
庫中導入的Manager
對象,它是咱們的具體實現細節。
咱們能夠經過實現SocketFactory
接口,來增長若干工廠類,只要咱們實現這個接口便可。
咱們在提供一個關於客戶端鏈接實例的抽象:
export interface SocketClient { connect(configuration: WebSocketConfiguration): Promise<any>; close(): Promise<any>; emit(event: string, ...args: any[]): Promise<any>; on(event: string, fn: Function): Promise<any>; }
而後再提供一些實現細節:
class WebSocketClient implements SocketClient { private socketFactory: SocketFactory; private socket: any; public constructor(webSocketFactory: SocketFactory) { this.socketFactory = webSocketFactory; } public connect(config: WebSocketConfiguration): Promise<any> { if (!this.socket) { this.socket = this.socketFactory.createSocket(config); } return new Promise<any>((resolve, reject) => { this.socket.on('connect', () => resolve()); this.socket.on('connect_error', (error: Error) => reject(error)); }); } public emit(event: string, ...args: any[]): Promise<any> { return new Promise<string | Object>((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<any> { return new Promise<any>((resolve, reject) => { if (!this.socket) { return reject('No socket connection.'); } this.socket.on(event, fn); resolve(); }); } public close(): Promise<any> { return new Promise<any>((resolve) => { this.socket.close(() => { this.socket = null; resolve(); }); }); } }
值得注意的是,這裏咱們在構造函數中,傳入了一個類型是SocketFactory
的參數,這是爲了知足關於依賴倒置原則的第一條規則。對於第二條規則,咱們須要一種方式來提供這個不須要了解內部實現細節的、可替換的、易於配置的參數。
這也是爲何咱們要使用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; }
這些註釋(裝飾器)僅僅會在代碼運行時,在如何提供這些組件實例時,提供一些元數據,接下來咱們僅僅須要建立一個依賴倒置容器,並將全部的對象按正確的類型綁定起來,以下:
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<SocketClient>(TYPES.WebSocketClient).to(WebSocketClient); provider.bind<SocketFactory>(TYPES.WebSocketFactory).to(SocketIOFactory); export default provider;
讓咱們來看看咱們如何使用咱們提供鏈接服務的客戶端實例:
var socketClient = provider.get<SocketClient>(TYPES.WebSocketClient);
固然,使用Inversify
能夠提供一些更簡單易用的綁定,能夠經過瀏覽它的網站來了解。
通常說到依賴倒置原則,每每第一個想到的術語便是依賴注入
,這種在各個技術棧都有應用,以後又會立刻想到spring
、ng
等先後端框架。
咱們確實是經過使用這些框架熟知這個概念的,可是若是你仔細想一想的話,是否還有其餘的一些場景也使用了相似的概念呢?
好比:
express
、redux
也許有的人會不一樣意個人觀點,會說依賴注入通常都是面向類和接口來說的,這確實有必定的道理,可是我認爲沒有必要侷限在一種固定的模式中去理解依賴倒置,畢竟它是一種思想,一種模式,在js中,全部的東西都是動態的,函數是一等公民,是對象,那麼把這些與依賴倒置原則聯繫起來,徹底也講的通。咱們真正關心的是核心問題是如何解耦,把更多的注意力投入的真正的業務邏輯中去。
歡迎關注公衆號 全棧101,只談技術,不談人生