OSSから設計について学ぶ〜クラスの依存方向について〜
要約
僕が間違ってました。すみませんでした。
いきさつ
「Mapを使ったアプリケーションで、Mapの上にMarkerを立てたい」
という要件を満たすために、leafletというOSSを使うことにした。
- 公式サイト
インターフェース(ライブラリの使い方)を予想してみた
OSSを使うときに、
こんな感じのインターフェースじゃないかなって予想してみた。
僕「MapがMarkerを持ってるから、こうだろ」
僕「だから、インターフェースはこんな感じだろ」
const map: Map = new Map(); const marker: Marker = new Marker(); map.addMarker(marker);
僕「で、実装はこんな感じだろ」
class Map { private markers: Array<Marker>; constructor() { this.markers = []; } addMarker(marker: Marker) { this.markers.push(marker) } } class Marker { constructor() { } }
僕「うーむ。実に自然な発想だぞ。」
実際のインターフェース
const map: Map = new Map(); const marker: Marker = new Marker(); marker.addTo(map);
僕「え…?」
class Map { constructor() { } } class Marker { private map: Map constructor() { } addTo(map: Map) { this.map = map; } }
僕「え…?え...?」
Mapクラス「もしかして」
Markerクラス「わたしたち」
僕「(依存方向が)いれかわってる〜〜〜〜!?」
他のOSSもみてみた
google map JavaScript API、mapboxGL, mapboxJSなどもみてみたが、
皆同じようなインターフェースだった。
つまり、
MapがMarkerに依存しているのではなく、
MarkerがMapに依存していた。
どうしてこんな設計なのか考えてみた
こっちのほうが変更に強いからだった。
変更に強いとは
例えば、MapにMarkerじゃなくてPopupも追加しようと思ったら、
僕が考えた実装方法だったらこうなる。
※ 理解を簡単にするため、ソースコードは極限まで単純化しています
class Map { private markers: Array<Marker>; private popups: Array<Popup>; constructor() { this.markers = []; this.popups = []; // 追加 } addMarker(marker: Marker) { this.markers.push(marker) } // 追加 addPopup(popup: Popup) { this.popups.push(popup) } } // 追加 class Popup { constructor() { } }
つまり、Popupの機能を追加しようとしたときに、
Mapの方も修正が必要になってくる。
さらにOverlay(地図上に何かを覆い隠す)を機能として追加してみると、、、
いい感じに「やな感じ」がしてきましたね笑
やな感じの正体
新たな機能を追加するたびに、Mapの方にも修正が発生してしまっているというのが、
やな感じの正体です。
これは「開放・閉鎖の法則」に違反しています。
開放/閉鎖原則に沿ったソフトウェアは、既存のソースコードを変更せずに機能修正や機能追加を行うことができる。 そのため、品質検査を再実行する必要がない。(wiki)
また、MapとMarkerだけ使いたいときもあるはずです。
そのときも、MapはPopupやOverlayのことまで知っている必要がある、というのもおかしな話ですね。
依存関係を逆転してみる
class Map { constructor() { } } // 追加 class Popup { private map: Map constructor() { } addTo(map: Map) { this.map = map; } }
既存のコードに手を加えずに、機能を追加することができます。
すごい。すごいね。
どんなときに応用できるか
たとえば、ペイントのようなアプリをつくるとしましょう。
機能をボンボコ追加しても、修正をしても他のクラスには影響がでません。
素敵ですね。
まとめ
依存関係を逆転して考えると、
その後の機能追加時のコストに大きな差が出てきそうです。
そんなことを、今回知りました。
ただ、今回本当に単純化しているので、実際はもうちょっと複雑なはず。
注意したいです。