DIP――ブラウザの機能を、ポートの向こうへ置く
作成: / 公開: / 内容更新:
執筆:tamito0201 / 掲載・運営:プロマリ
丸い共有ボタンの裏側を掘る連載の第8回。SOLID の5つ目の原則 DIP を、「コピー」のボタン1つから確かめます。コピーを頼むコードはブラウザを知らず、約束(ポート)だけを知る。実行時の呼び出しは外へ、import は内へ。import の向きを数え、DI・DI コンテナ・ファクトリの役目を読み、わざと境界を破って何が止めるかまで、図16点で読み解…

こんにちは、プロマリの紫です。連載「丸いボタンの裏側」の第8回、第2章の5回目です。SOLID は、変更に強いコードを書くための5つの原則を、頭文字でまとめた呼び名でした。第2章では、その5つを1つずつ、この共有ボタンのコードに当てて確かめてきました。今回は最後の原則、DIPです。テーマはコピーを頼むコードは、ブラウザのことをどこまで知っていればよいかということ。「コピー」のボタン1つを入口に、コードの矢印の向きを数え、わざと境界を破って何が止めるのかまで確かめます。
点線の付いた用語は、その言葉を押すと詳しい説明が開きます。意味、身近なたとえ、この実装での使い方を順に読めます。キーボードではTabで用語へ移動し、EnterまたはSpaceで開き、Escapeで本文へ戻れます。各ページで最初に出る用語から参照できるようにしました。
この記事は連載「丸いボタンの裏側」の第8回(2.5)です。前回の2.4「ISP――コピーの代役に、いいねの保存まで求めない」で、能力ごとに小さく分けたインターフェースを先に読むと、今回の話がつながりやすくなります。連載全体の地図は、本編5ページ目の連載の目次からどうぞ。
この回は6ページあります。1ページ目で DIP の意味と、このプラグインの層の地図を確かめます。2ページ目では、プラグインを離れて、ボタンとデータベースの短い例で「逆転」の正体をつかみます。3ページ目でプラグインの実物に戻り、コピーの差し込み口と import の向きを数え、4ページ目で、実装を選んで渡す組み立て役(DI・DI コンテナ・ファクトリ)を読みます。5ページ目ではわざと境界を破って、何が止めるかを実測し、6ページ目で、何にでも差し込み口を挟まないための目安と宿題に進みます。図は16点です。ここで示す件数や出力は、すべて実際に手元で動かして測ったものです。
コピーを頼む側は、ブラウザを知らない
共有欄には、SNS のボタンと並んで「URLをコピー」というボタンがあります。押すと、いま読んでいる記事の URL がクリップボードに入り、設定の例どおりなら「URLをコピーしました」と小さな知らせが出ます。やっていることは、それだけです。
けれど、このボタンの裏では、ブラウザに用意されたnavigator.clipboardという機能を呼んでいます。ブラウザによっては使えないことがあり、ページの状態によっては書き込みを断られることもあります。テストを動かす Node には、そもそもクリップボードがありません。コピーという単純な操作ほど、外の事情に振り回されやすいのです。
そこで、このプラグインでは、コピーを頼むコードに、ブラウザの機能を直接呼ばせていません。代わりに、「文字列を書き込み、成功か失敗を返す」という約束だけを用意し、それを通して頼みます。約束の名前は ClipboardGateway です。身近なもので言えば、壁のコンセントです。家電は、壁の向こうの発電所や配線を知りません。知っているのは、コンセントの形だけです。発電所のほうも、その形に合わせて電気を届けます。この連載では、この差し込み口にあたる約束を、ポートと呼びます。
このコンセントのたとえが、この回でずっと使う、ただ1つのたとえです。コピーを頼むコード(家電)は、ポート(コンセント)の形だけを知っています。ブラウザ用の実装も、テスト用の代役も、壁の向こうで、そのポートの形に合わせて作られています。だから、テストのときは代役を差し込むだけで、コピーを頼むコードを1行も変えずに動かせます。
Martin が書いた、2つの文
DIP を言葉にしたのは、SOLID の原則を広めた人として知られる Robert C. Martin です。1996年6月、C++ Report という雑誌の連載コラムで、次の2つの文にまとめました。
| 原文 | この記事での言い換え |
|---|---|
| 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. | 約束が道具の細かい都合に合わせるのではなく、道具のほうが約束に合わせる |
ここで言う「上位」は、偉いという意味ではありません。コピーを頼む、共有の手順を決める、といったアプリケーションの方針を持つコードのことです。「下位」は、その方針を実際に動かす細かい道具、たとえばブラウザのクリップボードの機能です。そして「抽象」は、何をするかだけを決めて、どうやるかを決めていない部品を指します。「依存」は、あるコードが別のコードの名前を知っていて、それが無いと動かない関係のことです。
2つの文をまとめると、こうなります。方針のコードも、道具のコードも、あいだに置いた約束のほうを向く。方針のコードが道具を直接知る形をやめ、両方が約束を知る形にそろえる。これが DIP の中身です。
原典の例も「コピー」だった
面白いことに、Martin がこのコラムで最初に使った例も「コピー」でした。キーボードから打った文字を、プリンタへ書き出すプログラムです。Copy という関数が、ReadKeyboard でキーボードから1文字読み、WritePrinter でプリンタへ書く。とても素直な作りです。
困るのは、書き出す先をディスクのファイルにも広げたくなったときです。Copy の中に「プリンタならこちら、ディスクならこちら」という if 文を足すことになり、出力先が増えるたびに、Copy は細かい道具を次々に知っていきます。Martin は、この Copy こそが「文字を読み取って書き出す」という、いちばん再利用したい方針を持っているのに、具体的な機器に縛られて使い回せない、と指摘しました。そして、Reader(読むもの)と Writer(書くもの)という抽象を挟み、Copy はその2つだけを知る形に直して見せました。
このプラグインのコピーのボタンも、同じ形をしています。コピーを頼むコード(方針)は、ClipboardGateway(書くもの)という抽象だけを知り、ブラウザのクリップボード(機器)は、壁の向こうに置かれています。30年前のプリンタが、いまはブラウザに替わっただけです。
おさらい:このプラグインの四つの層
先に進む前に、このプラグインのブラウザ側のコードが、どう分かれているかを確かめておきます。連載の本編でも紹介したとおり、コードは役割ごとのレイヤーに分かれています。表示と操作を受け持つ presentation、共有の手順を受け持つ application、判断と約束を置く domain、ブラウザの機能を実際に呼ぶ infrastructure の四つです。そのほかに、起動するときに部品をつなぐ組み立て役として composition があります。
地図のいちばん大事なところは、矢印の集まり方です。application は domain を知り、infrastructure も domain を知っています。けれど、domain はほかのどの層も知りません。import の矢印は、すべて domain に向かって集まります。コピーの約束(ClipboardGateway)が置かれているのも、この domain の中の gateway というフォルダです。点線の矢印は、組み立て役が起動するときにつなぐ線で、コードの import とは別のものです。この地図を手元に置きながら、次のページへ進みましょう。
考えてみる:方針のコードが、ブラウザを直接呼んでも、今のところ動いているなら困らないのでは?
動いているあいだは困りません。困るのは、変えたくなったときと、試したくなったときです。ブラウザの機能は、ブラウザの種類や権限、テストの環境によって振る舞いが変わります。方針のコードがブラウザを直接呼んでいると、そのたびに方針のコードを開くことになり、テストではブラウザの代わりを差し込む口がありません。5ページ目で、実際に直接呼ぶ形に書き換えて、テストの何件が落ちるかを数えます。







このページ(1ページ目)の感想・質問・設計へのコメント