トップ «前の日記(2020-01-21) 最新 次の日記(2020-02-01)» 編集

2020-01-25 [長年日記]

_ Service Locator + Visitor Patternのようなものでrequire地獄を避ける

勘違いがあったらツッコミください。

前提

JavaScriptには扱いやすい名前空間がない

Ruby や PHP は global な名前空間に簡単に class を置くことができ、必要に応じて module や namespace を使ってその影響範囲をコントロールすることができる。

対して JavaScript は伝統的にはすべて global leak させるか、特定のオブジェクトの中にぶら下げるかしかなかった。require / import 登場以降の時代は、読み込んだものに対して「任意の名前を付けることができる」ようになったので特定のコードを書いているファイルの中では自分で名前の衝突を避けるという作法になった。

逆に言うと思った通りの名前を思った通りにあちこちに影響させることはできない。例えば特定のディレクトリ以下にある class を全部読み込んでおけばアプリケーション内のどこからでもその名前を指定してオブジェクトを利用するといった使い方をするには一手間必要になる。そこで Service Locator ですよ。

Service Locatorの役割

Service Locator は単に名前とオブジェクトを紐づけてくれる KVS の役割を果たすオブジェクトだと思っておけばよい。

ServiceLocator.add('<name>', object) のようなメソッド *1 でオブジェクトの登録を行い、ServiceLocator.get('<name>') のようなメソッドでオブジェクトの取得を行う。ものすごく単純にインターフェイスだけ書くとこう。

class ServiceLocator {
  /**
   * @param {string} name
   * @param {object} object
   */
  static add (name, object) {
  }

  /**
   * @param {string} name
   * @return {object}
   */
  static get (name) {
  }
}

この Service Locator を使うと、ありとあらゆるところで

const Klass = require('klass')

を書きまくらなければいけないという問題を避けることができるのと、文字列からオブジェクトの取得ができるので、Ruby で言う Object.const_get() のようなものも実現できる。

requireしまくる人とaddしまくる人を消す工夫

上のような ServiceLocator をただ入れるだけだと、どこかで ServiceLocator.add() しまくる人は必要になる。ということは

const Klass = require('klass')
const Klass2 = require('klass2')
..

しまくる人も必要になる。

ただし、require される側に以下のようなコードを用意すると、この問題は解消できる。

class Klass {
  static get name () {
    return 'Klass'
  }

  static accept (locator) {
    locator.add(this.name, this)
  }
}

※ accept が汎用的すぎると感じたら(感じるよね?) locate などの名前でもよい。

こうしておくと、例えば以下のように Service Locator と組み合わせることで、上で書いた「特定のディレクトリ以下の class をすべてまとめて require しておく」ことを「いい具合に add を呼ぶ機能」で実現できる。

const glob = require('glob')
const ServiceLocator = require('service-locator')

glob(__dirname + '/sub/*.js', (err, files) => {
  files.forEach((file) => {
    const klass = require(file)
    klass.accept(ServiceLocator)
  })
})

やっていることは以下。

  • ServiceLocator で利用するオブジェクトに対して特定のルールを用意しておく
  • 特定のルールを用意しているオブジェクトに対し、ServiceLocator そのものを投げてしまう

お互いがお互いのインターフェイスを知っていればできる方法。上のコードでは accept メソッドの有無の確認などはしていないので、実践投入の際にはいろいろ考えて書くこと。

この程度の行数で済むなら Ruby や PHP のような感じなカジュアルさで class を使えるようになったと言ってもよさそう。

Tags: JavaScript

*1 add は set や register みたいな場合もあり得る