🖋️

鉛筆とシャーペンでちょっとわかるDependency Injection(依存性の注入)

に公開

手書きの鉛筆とシャーペンの図
手描きです

この記事の対象

  • Java / C# / Kotlin / TypeScript など、
    クラスベースのオブジェクト指向言語を使っている初〜中級者
  • フレームワークのドキュメントで
    「Dependency Injection」「DI コンテナ」という言葉を見かけるが、
    いまいちイメージが掴めていない方
  • 説明で頻出する「車とエンジン」とか機械の例えがピンと来なかった方

前提知識

  • 「クラス」「コンストラクタ」「new でインスタンスを作る」といった
    基本的なオブジェクト指向の用語がわかること
  • コード例は Java で書いていますが、
    他の言語でも「疑似コード」として読んでもらえれば問題ありません

まずは「依存性」という単語から離れて

私がIT講師として、新人エンジニアの方々にJavaやSpring Bootを教えていた時のことです。 「依存性の注入(Dependency Injection)」について説明する段階に入ると、クラスの空気が一瞬で「?」で埋め尽くされます。「依存性?」「注入?」という顔です。
テキストを予習してきた受講生が「内容がさっぱりわからなかったんですが、依存性の注入って
危ない話じゃないですよね…?」と困惑気味に聞いてきたこともあります(マジの話です)
依存性と言われても…
日常生活で使う「依存」って注入していいイメージないですよね

そこで私はいつも謎を払拭するためにこの一言から始めることにしています。

「皆さん、テキストの該当ページを開いてください。そして、『依存性の注入』という言葉に打ち消し線を引いてください ズバッと勢いよくお願いします

えっいいの?という顔をされるんですが、いいんです。皆さんのテキストですし。

「世間ではこの呼び方なんですが、皆さんの誤解を避けるために依存オブジェクトの注入と書き直してください。もしくは『依存しているモノを注入』とか、装填(そうてん)でも良いです。テキストをメルなんとかで売るつもりでもないなら、『これならわかるかも』という表現で上書きしてください。」

このトークを皮切りに、私が教室で話していたわかりやすーいと評判のDIの話をここでもシェアしたいと思います。

Q.なんでDIしたいの?

A.DIがあると、カスタムできる🔧

まず、皆さんがそこそこ使う頻度のあるシャーペン/メカニカルペンシル🖋️と、鉛筆✏️の2つを想像してください。どちらも筆記用具、つまり文字を書くために使う道具ですよね。鉛筆はもうあんまり出番無いですかね。まー私もめったに使いませんけどね

まずは歴史の古い鉛筆のほうから。鉛筆は木(本体)と黒鉛(芯)が糊付けされて一体化している製品です。黒鉛の部分が無くなってきたら削って、また露出させて書けるようにする。一方で、鉛筆の進化系と言ってもよいシャーペンというのは「芯」を注入とか装填して、それをカチカチしながら押し出して鉛筆のように筆記する製品です。鉛筆なりシャーペンで紙の表面をなぞると黒鉛が紙にこすりつけられて線となるわけですが、線にも色々あります。例えば線の濃さですね。濃さは本体の部分じゃなく黒鉛で決まっています。つまり、線の濃さは黒鉛に依存しているのです。
シャーペンというのはこの「依存オブジェクト(黒鉛)の注入」「依存しているモノ(黒鉛)を装填」できる製品と言えるんです。DI文房具です。一方で鉛筆はそうではありません。もう最初から黒鉛が入っちゃってて取り出すこともできません。

鉛筆というのは部品を糊付けしたものですから一個にまとまったスマートな製品ですが困ったこともあります。例えば2Bの鉛筆を使っていて、「もっと色を濃くしたい」とか「大事なことだから赤く書きたいんだけどな」とかちょっとした要望が出てくると思うんです。それに対応するには新しい鉛筆を用意するほかありません。4Bとか赤い鉛筆とか、どんどん本数が増えます。
一方のシャーペンは色のついた芯や4Bの芯が個別に売っています。つまり芯を装填するだけで済むことになります。要望に従って芯の種類は増えますが、持ちやすくてお気に入りのシャーペン本体があれば使い回せて長く使えて嬉しいこともあるわけです。

どうでしょう、まずは「依存オブジェクトを注入できるからカスタム性が生まれる」。要望に合わせて仕様変更に対応しやすいのがDIのありがたーいお得ポイントの一つです。私が鉛筆を使わないのは「どんどん短くなって使い心地が変わる」ためですが、シャーペンであれば芯を入れれば解決するので、これもDIのおかげと言えますね。

A.DIがあると、元凶を見つけやすい

さて。みなさんはIT系と言えどシステムやサービスなど、モノをつくる人たちです。ですから製造業の人たちの気持ちになることが新発見に繋がることもあります。引き続き鉛筆を例にしましょうか。鉛筆の製造業の仕事を想像してみてください。ある日検品の人から「芯が折れて使い物にならないです🤷」という報告が上がってきたとしましょう。本当に、フツーに使っていて折れてしまったようです。叩きつけたとかでもなく。
大変ですねヤバいですね、これは鉛筆のなにが悪いのでしょう? 🙄
黒鉛の質が悪そうな気はしますが、黒鉛を挟んでいる木も怪しいかも?
もしかしたら木が歪んで黒鉛をへし折ってたりしない?
使っている接着剤が膨張するのもあり得るかも?

全部一体化しているせいで原因の分析が大変にめんどくさいのです。🤮

ここでは例として Java でコードを書いていますが、クラスベースのオブジェクト指向言語であれば、ほぼ同じ発想で読み替えられます。

// 密結合な鉛筆(DIではない状態)
public class Pencil {
    // 鉛筆の中で「黒鉛」や「木」をnewして、完全に固定してしまっている
    private Core core = new Core("HB", "Made In USA", "Best Quality"); 
    private Wood woodCase = new Wood("ヒノキ", "Made In Italy", "leggermente storto");
    private Glue glue = new Glue("普通の接着剤", "Made In Japan", "木材との相性は微妙");
    // 相性が微妙なglueのせい?ってwoodCaseの木材が"ちょっと歪んでる"!犯人はお前か!

    public void write() {
        // もし書けなかった時、coreが悪いのか、glueが悪いのか、woodCaseが歪んでるのか分からない!
        core.draw();
    }
}

これは一例ですが、実際に糊付けされた鉛筆のようにPencilの中に情報を詰め込みました。この鉛筆で原因の分析をしようと思ったら、Pencilクラスの中をわざわざ覗いてチェックしなきゃいけません。

一方でシャーペンは分解可能な部品の集まりです。芯をつかむ機構やバネがあったり、握る部分はゴムやシリコンで保護されています。「どの部品がどの機能の責任を持っているか」がハッキリしているので原因の特定がしやすいのです。シャーペンでは芯が出てこないというトラブルがあったとしましょう。他の芯を入れて解決したなら、元々の芯と相性が悪いみたい。どんな芯を入れてもダメなら、シャーペンそのものが悪いと言えそうです。先端が歪んでたりして😨

これも例えばJavaで書くとこんなかんじ。鉛筆の芯と分けるために、慣習的に英語で呼ばれているLeadという単語を使います。

// シャープペンシル(DIな状態)
public class MechanicalPencil{
    // 芯の「置き場所」だけ用意しておく
    private Lead lead;

    // 「芯」は中で作らず、外から渡してもらう(コンストラクタで注入!)
    // これが Dependency Injection です
    public MechanicalPencil(Lead lead) {
        this.lead = lead;
    }
    
    public void write() {
        // 芯の太さをチェック(バリデーション)
        if(lead.getWidth() != 0.5){
            throw new IllegalArgumentException("エラー:このシャーペンは太さ0.5mm専用です!");
        }
        // 芯に「書く」仕事をさせる
        lead.draw();
    }
}

ここでの関係を形式的に言うと、

  • MechanicalPencil は Lead に依存していて、
  • Lead が、MechanicalPencil にとっての dependency(依存先)

ということになります。

この記事では説明をわかりやすくするために、
「依存しているモノ(Lead)」「依存オブジェクト」といった言い方も混ぜています。

もう少し教科書寄りにまとめると、Dependency Injection(DI)は

  • クラスの中で依存先を new せず、
  • 外側のコードやフレームワークから渡してもらう(注入してもらう)

という設計パターンです。今回の例だと、
「MechanicalPencil が Lead を自分で作らず、コンストラクタの引数として受け取っている」
部分がまさに DI にあたります。

シャーペンの芯のことはMechanicalPencilコンストラクタで「注入してもらう」とか「装填してもらう」ことで別々に管理しています。

こうして作成したMechanicalPencilクラスは、mainクラスの中ではこんな書き方をすることになると思います。

public static void main(String[] args) {
    // 1. まず、芯(依存オブジェクト)を作る
    // 「0.7mmの芯つっこんだろ 平気やろ」
    Lead myLead = new Lead("HB", 0.7);

    // 2. シャーペンに注入(装填)する
    MechanicalPencil myPencil = new MechanicalPencil(myLead);
    
    // 3. 書いてみる
    myPencil.write(); 
    // -> 出力:「エラー:このシャーペンは太さ0.5mm専用です!」
    // 「あっ…」
    
    // 【解決策】
    // MechanicalPencilの中身を改造する必要はない。
    // 渡す「芯」だけを0.5mmに変えればいい
    Lead correctLead = new Lead("HB", 0.5);
    MechanicalPencil myPencil2 = new MechanicalPencil(correctLead);
    myPencil2.write(); 
    // -> 出力:サラサラ(正常に書けた)
}

この例は0.7mmの太さの芯をねじ込んでしまっています。MechanicalPencilクラスの中身を見てみるとわかるのですが、0.5mmの太さでない芯が入った場合はメッセージが出るように作りました。ですから、この場合はMechanicalPencilクラスの中を見ずに芯の太さを0.5mmに変更してあげることで即時に解決するわけです。

どうでしょうか、わざわざ中を覗かなくても原因がわかるのってスッキリしませんか?
いや見ればわかるし…そんなに変わらないんでは…」という方もいらっしゃると思います。実際の業務だとオブジェクト数はこんなもんじゃないですよ。あるオブジェクトの中に引数としてオブジェクトABCを渡すんだけどBはデータベースから引っ張ってきたユーザの友人情報のうち紹介コードを使っていないユーザーのIDでCは系列企業Xのサービスの情報を取得した結果で…みたいなことはまぁ、割とあります。わざわざ内部のコードを全部見なくてもいい、スマートなやり方のひとつということだけ覚えてもらえば十分です。
これはカスタム性があるから、原因の切り分け、つまり不具合を見つけやすくなるのです。これもDIによるありがたーい効果の一つなんですね。

DIの現実、どういうときに使うのか

先程の鉛筆とシャーペンのたとえで「接着剤でくっつけちゃった後で中身見るのしんどそうだな…」という感覚が伝わっていれば大丈夫です。このへんは専門的な言葉で密結合疎結合という言葉を使うこともありますが、まずは言葉の暗記よりも「内部で色々決めちゃうと大変…」という感覚からバッドノウハウを避けるようにしてみてください。
密結合と疎結合を、鉛筆とシャーペンで例える図
鉛筆は密結合、シャーペンは疎結合のイメージ

ただ、ここで話を終えると「鉛筆よりシャーペンが優れている」という印象が皆さんに残ってしまうので一つお伝えしたいことはあります。シャーペンはメンテナンス性を高くすることで今後の変化に対応しやすく、バグの原因を見つけやすい作りではありますが…部品ごとに役割分担を徹底させたために部品をそれぞれ別に作るコストが嵩むのです。一方の鉛筆は木の板に糊付けと黒鉛を複数本差し込んでドカンと作るため、一度「もうトラブルが発生しないし変更も絶対にしない設計図」が出来上がればローコストかつスピーディに鉛筆を生産し続けることができるんですね。

同じことがプログラミングをするうえで言えます。「他人に公開するつもりもないし、コードの量も多くない。3-4回使ったらもう出番は二度とないから変更をするつもりが全く無い」ようなコードだったら無理にDIを意識しなくても良いのです。DIは顧客や自分たちが長年使うサービスで、後で改善する可能性があるときに役立つ概念なのだと私は考えています。その長年使うサービスというのが、みなさんが学んでいくwebフレームワークだったりゲームエンジンだったりするわけです。

おわり

このお話をシェアしたいと思った理由

既存の教材だと「車に対してエンジンという依存性をInjectする」みたいなたとえがよくあると思うんですが、ゆーて皆さんそんなにエンジン交換します?構造変更したら陸運支局で検査受けるんですよ?そんなに今のエンジンに不満ですか?性能に不満あるならむしろ車ごと替えるでしょ?EV車に至ってはエンジンですらないけど、最近の初学者に伝わるの?? と思っていました。

幸い今までの受講生からは「他の書籍にあったエンジンの話よりは機能が簡単だし使い方もわかるシャーペンの例えが好みです」「鉛筆はさすがに小学校2年生まで使ってたのでわかりますよ!」などのフィードバックがあったのでベターな表現かなと信じています。

次回のネタはまだ考えていませんが、そのうちに。それでは!✋️

Discussion