固体原理の理解:依存性反転


これは、依存関係の反転とは何かを探索し、なぜそれは緩やかに結合され、テスト可能なソフトウェアを提供するのを助ける固体の原則を理解するシリーズの第1部です.
本を読むときや他の開発者と話をするとき、あなたは遭遇したり、言葉をしっかりと聞いたかもしれません.
そのような議論では、いくつかの人々はそれの重要性に言及し、どのように理想的なシステムは、これらの原則のすべての特性を持つ必要があります.
日常的なシナリオで作業するときは、アーキテクチャやどのように時間制限や上司の声を押すことなく良いデザインの決定を組み込むために時間を持っていない可能性があります.
しかし、これらの原則は、それらをスキップするだけではありません.ソフトウェアエンジニアはそれらを開発努力に適用すべきである.コードを入力するたびに本当の質問は、どのように正しくあなたのコードをよりエレガントになるように、原則を適用するかです.
ソリッドでは、良い(または固体)ソフトウェアアーキテクチャを作成するのに役立つ5つの基本原則があります.ソリッドは以下の文字です.
sはSRP(単一責任原則)の略です
OCP (オープンクローズ原則)の略
LSP(Lisskov置き換え原理)を表します
ISP (界面偏析原理)
Dディップ(依存逆転原理)
もともと1990年にRobert C. Martinによってコンパイルされて、これらの原則は、完全な反対側に悪い結合と小さなカプセル化でコードをしっかり結合したソフトウェアコンポーネントを構築する方法を提供します緩やかに結合されたコード、凝集性と真にビジネスの本当のニーズをカプセル化します.

Good practice is anything that reduces coupling, which improves testability, maintainability, and replace-ability.



これは重要な点です.原則は悪いプログラマを良いプログラマに変えることはないだろう.原則は判断で適用されなければならない.彼らが恣意的に適用されるならば、彼らが全く適用されないかのように、それはちょうど悪いです.

This is not only about design patterns any more. It’s about thorough evaluation of each domain problem and exercising pragmatic solutions to avoid critical code smells.


原則とパターンの知識をいつ、どこでそれらを適用するかを決定するための正当性を与えます.それらの原則は主にヒューリスティックですが、それらは共通の問題への常識的な解決です.実際には、彼らは何度も何度も繰り返し証明されている.したがって、彼らは常識で接近されるべきです.
この記事の残りのために、私は依存関係逆転原則を調査し始めます.

依存性反転。


A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.


それは何を言いますか.一方では、抽象化している.ソフトウェア工学と計算機科学において、抽象化はコンピューターシステムの複雑さを準備するための技術です.これは、人が現在のレベルの下により複雑な詳細を抑制するシステムと対話する複雑さのレベルを確立することによって動作します.その範囲は広いです、そして、複数のサブシステムをカバーします.

Thus when working with abstractions you work on a high-level view of your system. You only care about the interactions you can do and not how to do them.


一方、低レベルのモジュールや詳細があります.それらは反対です.それらは特定の問題を解決するために書かれたプログラムです.彼らの範囲は限られていて、しばしば単位またはサブシステムをカバーします.たとえば、MySQLデータベース接続を開くと、特定のスコープへのバインドとして低レベルと見なされます.
今これらの2つのルールを読んで何を意味することができますか?
依存関係の反転の背後にある本当の意図は、クライアントコードが変更されなければならない範囲において、単に別のものに変更する必要があるために、decoupleオブジェクトに対するものです.それは、その構成要素の各々が他の別々のコンポーネントの定義のほとんどまたは全く知識を有しないかまたは使用するように、緩く結合を達成する.ゆるく結合されたシステムのコンポーネントが同じサービスを提供する代替の実装によってもとへ戻されることができるので、それはテスト容易性と交換可能性を成し遂げます.
依存関係の反転の欠点は依存関係インジェクションコンテナを必要とすることです.このコンテナーは、必要な場所に適切なスコープと右のパラメーターを使用してサービスを注入するのに必要な機能を持ちます.

すごい!では、どうやって始めるのですか?


JavaScriptの素晴らしいライブラリを使用して、実際には依存性の反転について学ぶことができます.

Inversify.js. A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.


この例では、WebSocket接続のサービスを提供する方法を示します.
例えば、サーバーに接続して更新を受け取るクライアントとWebソケットを公開するWebサーバーがあるとしましょう.現在、プレーンWebSocketを提供するいくつかの解決策があります.ソケットがあります.IO , SOCKS ,プレーンWebSocket , etc .それらのそれぞれは、異なるAPIまたは異なるメソッドを呼び出すことができます.私たちが何らかの方法でWebSocketプロバイダーの全体的な考えを抽象化したなら、それはよいでしょうか?このようにして、いくつかの種類の設定を受け入れ、必要に応じてWebSocket接続を作成する別のWebSocket CreatorまたはSocketFactoryを提供できます.
まず、インターフェイスを定義しましょう.
export interface WebSocketConfiguration {
  uri: string;
  options?: Object;
}

export interface SocketFactory {
  createSocket(configuration: WebSocketConfiguration): any;
}
ここではインターフェイスは何もないことに注意してください.我々は、それらが我々の抽象化であると言います.
ソケットのIOファクトリが欲しいとしましょう.
import {Manager} from 'socket.io-client';


class SocketIOFactory implements SocketFactory {
  createSocket(configuration: WebSocketConfiguration): any {
    return new Manager(configuration.uri, configuration.opts);
  }
}
これは、ソケットIOライブラリからのマネージャを指定するため、具体的で抽象的ではありません.それが我々の詳細です.
SocketFactoryインターフェイスを実装している限り、より多くのファクトリクラスを追加できます.
私たちが工場を持っているので、このWebSocket実装の抽象化を提供する方法が必要です.
新しい抽象化でもう一度始めましょう
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のパラメーターをコンストラクタに渡す方法に注目してください.これは依存性反転の最初の規則です.第2の規則のために、それに関する詳細を知らないで、交換するか、または構成するのが簡単であるこの値を提供する方法を必要とします.
それは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;
それだ!SocketClientのインスタンスを使用するたびに、右のバインディングでコンテナを呼び出します.
var socketClient = provider.get<SocketClient>(TYPES.WebSocketClient);
もちろん、Inversifyはシンプルなバインディングよりも提供しています.私はあなたのウェブサイト上で頭をお勧めし、それをチェックアウト.

反転する。js 演習


依存関係の反転コンテナを提供する他のライブラリが存在するかどうかを確認します.
  • 例では、' singleton 'スコープを使用してコンテナをインスタンス化しました.私がそれを指定しないならば、何が起こりますか?もう一つの方法は、私はInversifyを使用してそれを行うことができますか?
  • あなた自身のプロジェクトまたはウェブサイトを見て、あなたのサービスに依存性反転を利用する方法を考えてください.例API呼び出し、約束など
  • Extraクレジット:独自のDIコンテナライブラリを実装します.キーと値を受け入れるクラスまたはデータ構造を持ちます.キーの場合は、名前を指定し、値の場合は解決したインスタンスを指定します.Singletonのような、または工場としてのスコープを指定するメソッドを追加しようとします.
  • 回収する


    私はあなたがDIが理解されて、あなたがその特性に気づいたことを望みます.次の記事をお持ちください.

    参考文献



    SOLID Principles
    Wiki
    Getting a SOLID Start
    Another Wiki

    ばかから固体まで 次に来ることは、しっかりした原則を理解しています:一つの責任


    このポストが役に立つならば、それを共有してください、そして、私の他の の上で調整されていてください.あなたはarticlesと私に従うことができます.任意のアイデアや改善を持っている場合は私と共有すること自由に感じる.
    ハッピーコーディング.
    によるAugstin Lautaroによるカバー写真