读懂 SOLID 的「依靠颠倒」准绳

这是明白
SOLID准绳中,关于
依靠颠倒准绳怎样协助我们编写低耦合和可测试代码的第一篇文章。

写在前头

当我们在念书,或许在和一些别的开辟者谈天的时刻,能够会谈及或许听到术语SOILD。在这些议论中,一些人会说起它的重要性,以及一个抱负中的体系,应该包括它所包括的5条准绳的特征。

我们在每次的事情中,你能够没有那末多时候思索关于架构这个比较大的看法,或许在有限的时候内或催促下,你也没有要领实践一些好的设想理念。

然则,这些准绳存在的意义不是让我们“跳过”它们。软件工程师应该将这些准绳运用到他们的开辟事情中。所以,在你每一次敲代码的时刻,怎样能够准确的将这些准绳付诸于行,才是真正的题目所在。假如能够那样的话,你的代码会变得更文雅。

SOLID准绳是由5个基础的准绳构成的。这些看法会协助制造更好(或许说更硬朗)的软件架构。这些准绳包括(SOLID是这5个准绳的开首字母构成的缩略词):

早先这些准绳是Robert C. Martin在1990年提出的,遵照这些准绳能够协助我们更好的构建,低耦合、高内聚的软件架构,同时能够真正的对现实中的营业逻辑举行适可而止的封装。

不过这些准绳并不会使一个低劣的程序员转变为一个优异的程序员。这些轨则取决于你怎样运用它们,假如你是很随便的运用它们,那等同于你并没有运用它们一样。

关于准绳和形式的学问能够协助你决定在何时何地准确的运用它们。只管这些准绳仅仅是启发性的,它们是常见题目的通例处置惩罚方案。实践中,这些准绳的准确性已被证清楚明了很屡次,所以它们应该成为一种基本知识。

依靠颠倒准绳是什么

  • 高等模块不应该依靠于初级模块。它们都应该依靠于笼统。
  • 笼统不应该依靠于完成,完成应该依靠于笼统。

这两句话的意义是什么呢?

一方面,你会笼统一些东西。在软件工程和计算机科学中,笼统是一种关于计划计算机体系中的庞杂性的手艺。它的事情原理平常是在一个人与体系交互的庞杂环境中,隐蔽当前级别下的更庞杂的完成细节,同时它的范围很广,常常会掩盖多个子体系。如许,当我们在与一个以高等层面作为笼统的体系合作时,我们仅仅须要在乎,我们能做什么,而不是我们怎样做。

别的,你会针对你的笼统,有一写初级别的模块或许详细完成逻辑。这些东西与笼统是相反的。它们是被用于处置惩罚某些特定题目所编写的代码。它们的作用域仅仅在某个单位和子体系中。比方,竖立一个与MySQL数据库的衔接就是一个初级别的完成逻辑,由于它与某个特定的手艺领域所绑定。

如今细致读这两句话,我们能够获得什么暗示呢?

依靠颠倒准绳存在的真正意义是指,我们须要将一些对象解耦,它们的耦合关联须要到达当一个对象依靠的对象作出转变时,对象自身不须要变动任何代码。

如许的架构能够完成一种松耦合的状况的体系,由于体系中一切的组件,彼此之间都相识很少或许不须要相识体系中其他组件的详细定义和完成细节。它同时完成了一种可测试和可替代的体系架构,由于在松耦合的体系中,任何组件都能够被供应雷同效劳的组件所替代。

然则相反的,依靠颠倒也有一些瑕玷,就是你须要一个用于处置惩罚依靠颠倒逻辑的容器,同时,你还须要设置它。容器一般须要具有能够在体系中注入效劳,这些效劳须要具有准确的作用域和参数,还应该被注入准确的实行高低文中。

以供应Websocket衔接效劳为例子

举个例子,我们能够在这个例子中学到更多关于依靠颠倒的学问,我们将运用Inversify.js作为依靠颠倒的容器,经由过程这个依靠颠倒容器,我们能够看看怎样针对供应Websocket衔接效劳的营业场景,供应效劳。

比方,我们有一个web效劳器供应WebSockets衔接效劳,同时客户端想要衔接效劳器,同时接收更新的关照。当前我们有多少种处置惩罚方案来供应一个WebSocket效劳,比方说Socket.ioSocks或许运用阅读器供应的关于原生的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能够供应一些更简朴易用的绑定,能够经由过程阅读它的网站来相识。

译者注

平常说到依靠颠倒准绳,每每第一个想到的术语等于依靠注入,这类在各个手艺栈都有运用,以后又会立时想到springng等前后端框架。

我们确切是经由过程运用这些框架熟知这个看法的,然则假如你细致想一想的话,是不是另有其他的一些场景也运用了相似的看法呢?

比方:

  • 一些运用插件和中间件的框架,如expressredux
  • js中this的动态绑定
  • js中的回调函数

或许有的人会差别意我的看法,会说依靠注入平常都是面向类和接口来说的,这确切有肯定的原理,然则我以为没有必要范围在一种牢固的形式中去明白依靠颠倒,毕竟它是一种头脑,一种形式,在js中,一切的东西都是动态的,函数是一等国民,是对象,那末把这些与依靠颠倒准绳联系起来,完整也讲的通。我们真正体贴的是中心题目是怎样解耦,把更多的注意力投入的真正的营业逻辑中去。

迎接关注民众号 全栈101,只谈手艺,不谈人生

《读懂 SOLID 的「依靠颠倒」准绳》

    原文作者:littleLyon
    原文地址: https://segmentfault.com/a/1190000012929864
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞