Help us understand the problem. What is going on with this article?

Javascript: classやinterfaceを知らない人に伝えたい依存性逆転の原則(初級者向け)

最近、勉強会をする機会があったり、部下を育てるような話が出てきたのでSOLID原則についてまとめようと思いました。
これ、Javascriptばかり書いている人に伝えようと思っても、そもそもクラスつかってなかったり、インターフェースなんてないし、なんて人も多いので結構説明に困るんですよね。。。

なので、javascriptでの簡単な事例を通してSOLID原則を語るシリーズを書くことにしました。

今回は SOLID原則の 「D」 依存性逆転の原則(dependency inversion principle)です。


依存ってなんだっけ?

データに依存している例
user.json
{
  "lastName:" "東京",
  "firstName": "一郎",
}
main.js
import axios from 'axios'

const { data } = axios.get('/path/to/user.json')
// 処理
console.log(`${data.lastName}${data.firstName}`)

この処理の部分はデータ構造に依存しています。つまりfirstNamelastNameというデータを知っている状態です。

ライブラリに依存している例
import $ from 'jQuery'
const div = $('div')[0]

この処理はjQueryに依存しています。import文を取ったら$ is not definedと怒られるでしょう。

モジュールに依存している例
main.js
import TopHeader from 'TopHeader'
import TopFooter from 'TopFooter'
TopHeader.init()
TopFooter.init()

これはヘッダーとフッターを作るモジュールに依存しています。これもimport文を取ったら動かなくなりますね。

依存すると何が問題なのか?

問題
データに依存している データの構造が変わったら処理全部書き直し
ライブラリに依存している 例えば、脱jQueryしたいときに書き直さなくちゃいけないコードが多すぎて辛い、とか
モジュールに依存している例 やっぱ使うモジュール変えたいってなったら書き直し

やっぱ変えよう、となった場合にいろいろな箇所を書き直す必要が出てきます。

じゃあどうすればいいのか?

データに依存しない例
user.json
  {
    "lastName": "東京",
    "firstName": "一郎",
  }
user.js
  import axios from 'axios'

  const { data } = axios.get('/path/to/user.json')
  const name = `${data.lastName}${data.firstName}`

  export default {
    name
  }
main.js
  import user from '/path/to/user.js'
  // 処理
  console.log(user.name) // 東京一郎

user.jsを読み込みさえすれば、処理側ではnameというデータを利用するだけでよくなりました。実際のデータの詳細は知りません。

ライブラリに依存しない例
dom.js
import $ from 'jQuery'
export default {
  ref: (selector) => {
    return $(selector)[0]
  }
}
main.js
import dom from '/path/to/dom.js'

const div = dom.ref('div')

dom.jsを読み込んでいれば、dom.ref(selector)でDOMにアクセスできるようになりました。jQueryを使っているかどうかは関係ありません。
もし、脱jQueryしたければdom.jsrefの実装を変えるだけです。

モジュールに依存しない例

main.js
import TopHeader from 'TopHeader'
import TopFooter from 'TopFooter'
import createParts from 'createParts'
createParts(new TopHeader(), new TopFooter())
createParts.js
export default = (header, footer) => {
  header.init()
  footer.init()
}

処理自体はcreateParts.jsに切り出しました。受け取る引数はどんなヘッダーやフッターだろうとinit()でパーツが生成されればいい作りになっています。`TopHeaderTopFooterなど、決め打ちでモジュールに依存することは無くなりました。

3つの例に共通する大事なことは、実際に使う側のルールと合わせているということです。

ルール
データに依存しない 使う側では name に 氏名 が入っていればいい
ライブラリに依存しない dom.ref(selector)でDOM参照が取れれば実装はなんでもいい
モジュールに依存しない init()を実行することでパーツが作られればいい

依存関係を図にしてみます(→の方向に依存があります)

データに依存しない例

data.png

ライブラリに依存しない例

lib.png

モジュールに依存しない例

module.png

間に処理を挟み込むことで、依存している方向を示す矢印が上むきだったのが下向きに逆転しているのがわかるでしょうか?

使う側からすれば、使う側のルールにしたがって作ればその中のことは考えなくていいわけです。

何が嬉しいのか

ルール
データに依存しない データの構造が変わっても、間に挟んだ変換処理を修正するだけ
ライブラリに依存しない 脱jQueryしたいときは、間に挟んだ変換処理を修正するだけ
モジュールに依存しない やっぱ使うモジュール変えたいってなったら、引数を修正するだけ

classやinterfaceを知らない人向けに書いたので、インターフェースやDIについての詳細は極力排除して見ました。

なので若干無理やりな表現もあるかと思います、が本質はそんなにずれてないかと思っています。

もし、「ここ違うんじゃないの?」みたいなことがあれば、柔らかめのマサカリを投げていただけると幸いです。

「データに依存しない例が、axiosに依存しているじゃないか」という部分については許してください

SeijiNishiwaki
元々webサイトの保守をしていましたが、成長に限界を感じてJavaの世界に飛び込んだものの、やっぱりフロントエンドがやりたくなって戻ってみたらいろんなことが変わっていて楽しい毎日です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away