雰囲気オブジェクト指向プログラミングシリーズです。
オブジェクト指向プログラミングの成長は、コードの変化もさることながら、コードへの認識が変わることも重要に感じています。そこで、認識の変化を分かりやすくするため、あえてコードなしで、絵だけで視覚化して、成長段階を説明してみたいと思います。
この過程はあくまでも私の経験ベースによるものなので人によっては違うかもしれません。
1. 継承
#4472c4 |
親 |
#ed7d31 |
拡張 |
継承を覚えた瞬間のイメージですね。
クラスに何かを加えてクラスを大きくしてるイメージです。
2. 処理を分割しただけの委譲
#5b9bd5 |
処理全体 |
#ed7d31 |
処理の一部を委譲したクラス |
大きな処理の一部を、クラスに切り出してるだけですね。切り出したクラスも含めて処理全体と認識してる段階です。
「継承よりも委譲がいい」と言われてコードを見ても、それが当たり前に見えて良くわからない。
委譲というイメージが湧いていません。
3. 処理を分割しただけ ver Web MVC
#5b9bd5 |
Controller |
#ed7d31 |
Service的なモノ |
#ffc000 |
DB(ORM) |
責務的なイメージが最初に沸いたのは、レイヤー構造だったので、馴染みのあるWeb MVCとしてみました。
Controllerという処理をただ分割していました。
3. オブジェクトの役割(責務)に気付く
#4472c4 |
Controller |
#ed7d31 |
Service |
#ffc000 |
DB(ORM) |
Controllerの役割から、オブジェクトの役割に気付き始めます。
コードへの認識が大きく変わります!
- Controllerは、Hogeサービスに、処理を依頼する。
- Controllerは、Hogeサービスの結果によって、プログラムの流れをハンドリグする。
- Hogeサービスは、その処理を実行する実際のサービス。
みたいな感じです。
3. 2. オブジェクトの役割に気付いたけど、レイヤ汚染
#4472c4 |
Controller |
#ed7d31 |
Service |
#ffc000 |
DB(ORM) |
少し脱線。レイヤー構造の役割に気付くことで、レイヤーの違反が気になるようになります。
- FormオブジェクトをServiceに渡してしまう。
- SessionオブジェクトをServiceに渡してしまう。
悩みましたが、腑に落ちる方法で解決出来るようになったのは、かなり後でした。
良い解決策をお探しの方は「クリーンアーキテクチャ」等を読むと良いかもしれません。
4. 共通化のせいで"処理で分割"に戻る
#4472c4 |
Controller |
#ed7d31 |
個別っぽいSevice |
#5b9bd5 |
共通Service |
#ffc000 |
DB(ORM) |
処理の共通化を強く考えはじめます。
SOLID原則等も何となく知り、「単一の責務」は分かるのですが、ピンと来ません。
共通処理という一つの責務?みたいに、まとめてしまうコードになりました。
5. InterfaceでDIしたけど何の意味があるかわからない
#4472c4 |
Controller |
#ed7d31 |
個別っぽいSevice |
#70ad47 |
Interface |
#5b9bd5 |
共通ServiceImpl |
#ffc000 |
DB(ORM) |
DIを知ったきっかけで、Interfaceを利用し始めます。
実装クラスの前に無駄にメソッドだけが定義されたクラスが挟まる。というイメージしか持てません。
テストのし易さで無理やり納得してました。
6. Interfaceも相互作用のオブジェクトだと認識する
#4472c4 |
Controller |
#ed7d31 |
個別っぽいSevice |
#70ad47 |
Interface |
#5b9bd5 |
共通ServiceImpl |
#ffc000 |
DB(ORM) |
利用側の視点が持てると、Interfaceもオブジェクトの一つだと認識できるようになります。
コードへの認識が大きく変わります!
ドメイン駆動設計の「Repository」の典型的なDIPパターンでイメージを掴む方も多いですかね。
私は「インタフェース指向設計」というマイナーで少し古い本で理解しました。
7. 全体の流れを意識出来ようになる
#ed7d31 |
UseCase |
#70ad47 |
共通デカいInterface |
#AACCCC |
流れとして再認識してみる |
#fbe5d6 |
処理の流れを組む様々なオブジェクト群が必要 |
絵はふわっとしてるので、次も一緒に見たいほうがいいかも。
ユースケースシナリオ等の要求仕様や、処理の全体の流れを意識したコードになります。
全体の流れを意識しつつ、オブジェクトを組み合わせていくというイメージが持ち始めます。
このイメージがあると、共通処理でまとめて無理やり分割してたことが、クラスの歪みの原因だと気づきます。
オブジェクト指向分析/設計系の知識も必要になります。まんまの「ユースケース駆動開発実践ガイド」がお勧めです。「オブジェクトデザイン」のステレオタイプもおすすめです。
8. 責務を意識したクラス分解(オブジェクト指向設計)
#ed7d31 |
UseCase |
#fbe5d6 |
処理の流れを組む様々なオブジェクト群が必要 |
#70ad47 |
Interfaceを利用したオブジェクト |
#5b9bd5 |
独立した小さなオブジェクト |
#a260bf |
本当に必要だった共通オブジェクト |
オブジェクトの責務を意識した「小さく独立したクラスの重要性」のイメージを持つと、オブジェクトを組み合わせつつ全体の流れを作るという設計ができるようになります。
表現のためにクラス構造を変えるという手法も必要になり、DIPやポリモーフィズム等のオブジェクト指向設計を自然に活用したクラス分割が始まります。前段階の7に至る時点で、実はこのイメージも必要な気がします。
8. 2. 並び変えただけ
#ed7d31 |
UseCase |
#CCAd81 |
処理の流れを叶えるための様々なオブジェクト群 |
#70ad47 |
Interfaceを利用したオブジェクト |
#5b9bd5 |
独立した小さなオブジェクト |
#a260bf |
本当に必要だった共通オブジェクト |
流れっぽく並べ替えただけです。
9. Web MVCを再統合して
#4472c4 |
Controller |
#ed7d31 |
UseCase |
#70ad47 |
Interfaceを利用したオブジェクト |
#5b9bd5 |
独立した小さなオブジェクト |
#a260bf |
本当に必要だった共通オブジェクト |
#ffc000 |
DB(ORM) |
せっかくの絵なのでWeb MVCと統合して、それっぽくしてみました。
このレイヤ分解が正解という意図はなし。
10 おまけ 実はコードの構成は変わってない
レイヤ視点でのコード構造的には実は何も変わっていません。
まさに、認識によってコードの見え方が変わっているだけなのです。
おわり
プログラミングのコードは、ぱっと見で構造的に理解しても、認識の違いによって大きく意図が変わってしまうことが多々あります。デザインパターンなどまさにそれですね。
逆手に取ってフィットアンドギャップに利用したのがドメイン駆動設計だと考えると、それが面白い試みだなと気づかされます。
ただ、この認識の違いこそが、オブジェクト指向プログラミングの学習の難しさに繋がっているのかなと思い付き、記事を書いてみました。
イメージだけにイメージが伝わると幸いです。