オブジェクト指向の本当の姿 後編 @Tomofiles
目次
後編の導入
こんにちは、Tomofilesです。
前編の記事で、ドメイン駆動設計(DDD)の紹介と、圧倒的な抽象化、オブジェクト指向の本当の姿について、お話しました。
過激なタイトル気味に本当の姿と謳いつつ、実際はいろんな考え方・アプローチがあるので、一様ではないのですが、重要なのはオブジェクト指向自体ではない、という点は、本当の姿としては、どんな考え方・アプローチにも当てはまると、私は考えています。
さて、後編では、難易度・コストが高いというDDDから、他の開発方法に取り入れても、有効な考え方・アプローチを抽出して紹介します。
DDDを部分的に実践するのは、軽量DDDといい、実はアンチパターンです。導入に対する効果は高くないです。
ですが、視点を変えて、ソフトウェアの構成として、どんなときにも当てはまる(また過激…)と個人的に思うテクニック、という形であれば、有用ではないでしょうか?
この記事が、初心者エンジニアの皆さんの参考になれば、幸いです。
DDDから学べること
DDDをしっかりと学ぶのは、非常に大変です。
私も「エリック・エバンスのドメイン駆動設計(いわゆる、原典)」と「実践ドメイン駆動設計」は読んだのですが、どちらも3センチくらいの厚みがあるブロック本だったので、かなり骨が折れました。(そして、全部が理解できているわけではない…)
DDDを個人的に学習する中で、私が現在も重要視していて、仕事の中でも実践して効果を感じている、テクニックをいくつか見つけました。
今回の記事のテーマでもある、プログラムがどのように見えているのかという点にもつながります。
オブジェクト指向プログラミングの文脈でも、表現を変えて語られていることではあるのですが、DDDの文脈から見たほうが、効果が実感しやすいと個人的に思います。
この記事では、そんなテクニックから1つ、紹介したいと思います。
境界と公開
「境界」という単語は、DDDの文脈では多く登場します。
境界は、オブジェクト指向の文脈では、スコープ、パッケージ、アクセス修飾子、などが、関連するトピックでしょうか。
オブジェクト指向の文脈においては、オブジェクトをカプセル化する手段として、フィールドやメソッドにprivate
やpublic
、ときに何も付与しない、など修飾を行い、アクセスできる対象を制御することができる、というものです。
private
やpublic
はまだわかりやすいのですが、Javaで用いられる何も付与しない(パッケージ・プライベート)というのは、なかなかピンとこないものです。
パッケージ・プライベートは、Javaの言語仕様のパッケージ(簡単に言うと、同じフォルダ内)のクラスにのみ、アクセスを許可するというものです。
参画しているプロジェクトのソフトウェア構成によっては、パッケージ・プライベートが一切活かせない構成になっているところも少なくないため、実はあまり使っていない人も多い言語仕様です。
パッケージ、アクセス修飾子の具体的な使いどころについては、残念ながらJavaの言語仕様としても、詳細に定めていません。(と、思います。私は見たこと無いです)
Effective Javaにおいても、公開APIとする必要がない限り、パッケージ・プライベートであるべき、ぐらいの記述しかありません。
Effective Java 第2版 項目13 クラスとメンバーへのアクセス可能性を最小限にする
もし、トップレベルのクラスやインターフェースをパッケージプライベートにできるなら、そうすべきです。
さて、DDDの境界はどうでしょうか?
DDDにおける境界は、境界付けられたコンテキストという用語で用いられています。
境界付けられたコンテキストもまた、抽象的でややこしい概念なのですが、簡単に説明すると、ビジネス上の知識の適切なまとまりであり、同じ言葉を話す(ユビキタス言語という)関係にあるもの、というところでしょうか?
ユビキタス言語は、とりあえずわからなくても大丈夫です。
とにかく、ここで重要なのは、DDDにおける境界とは、プログラム、ソフトウェアの仕組みとしてのスコープの話ではなく、ビジネス上の業務的なスコープの話であり、AというリソースとBというリソースは、業務的に関連があるよね、ないよね、という感じで、視点が違うのです。
業務的な関連性があるということは、例えばA、Bというリソースがあったとき、一回のユーザーの要求で、必ず両方のリソースを同時に変更したり、一方のリソースの処理結果を、必ず他方のリソースに反映したりと、関係性として密であるという特徴があります。
関連性のあるもの同士を同一のパッケージに入れて、そのパッケージを境界付けられたコンテキストとして扱うことで、凝集度(機能要素と情報要素間の関連性の高さ)の高いソフトウェアが構築できるのです。
◆コラム(難易度高め)
しかしながら、単純に関連のあるオブジェクトをすべて同一パッケージに入れてしまうと、超巨大なエンティティの塊ができてしまうので、そこは、さらなるモデリングのテクニックが、DDDには用意されています。
例えば、エンティティから別のエンティティを抽出して分離し、エンティティの識別子だけ保持するなど。分離すると、両者の関係性が出てくるので、また複雑になりますが。
奥が深い…
前編の記事で、2つのエンティティを同時に更新してはいけないとお話したのは、これが理由です。
状況によりますが、境界付けられたコンテキストとエンティティは、一対一と考えたほうがシンプルです。
関連のあるリソースは、一つのエンティティにまとめて、外部から見たときに中身が見えないようにしておくことが重要です。このときに、パッケージ・プライベートが有効です。
価値を消費する側のオブジェクトから、価値を提供する側のオブジェクトの構成は見えないですが、内部では関連性のあるオブジェクト同士が協調して機能を実現し、境界の外部には抽象度の高いAPIをpublic
スコープで公開します。
ここまでのDDDにおける、エンティティと境界付けられたコンテキストから学ぶことができるのは、ある機能をソフトウェアに実装する際に、関連したオブジェクトは一つのパッケージに詰め込み、外部に公開するAPIのみを定義したインターフェースを設ける、というテクニックです。(Javaの言語仕様におけるinterface
と同義です)
現在はアンチパターンとして有名になりつつありますが、機能横断的に、サービスのみが入っているパッケージ、インターフェースだけが入っているパッケージ、DTO(データオブジェクト)だけが入っているパッケージ、といった構成ではなく、同じ機能を構成する、サービス、インターフェース、DTOは、同じパッケージに入れる、ということです。
インターフェース経由でAPIを実行することで、パッケージ内部が隠蔽されて、実装の詳細に関心が向かないようにすることができます。
そして、実装の詳細を隠蔽したまま、オブジェクトの実体を生成する方法として、ファクトリーという役目のオブジェクトを用意して、public
スコープで公開します。
ファクトリーからオブジェクトの参照を取得し、インターフェース型の変数で受け取り、インターフェースのメソッド(API)を呼び出して、機能を利用することで、パッケージ内の実装の詳細は一切外部にもれなくなります。
オブジェクト指向的な考え方ができる人の頭の中では、上図のような、境界と公開するモノのイメージ図が、見えています。
何をひとまとめにして格納しておき、どこを外部に公開することで、高凝集になるか? という思考回路が、頭の中にできているのですね。
おわりに
思ったより、後編が短くなりました。
本当は、もうちょっと色々書こうと思ったのですが、どのトピックも簡単に触れるには奥が深いもので、ちょっと短くまとめるのが難しくてやめました。
ちなみに、諦めたトピックは以下です。
- 継承 vs. コンポジション&委譲
- レイヤー化と依存関係の逆転
まぁ、いずれ別で記事を書こうと思うので、よかったらお待ちください。
さて、前編、後編通して、オブジェクト指向についてお話してきました。
オブジェクト指向には、もっと直接的に有効な原則として、SOLID
というものがあります。
SOLID
は、ソフトウェア設計における5つの重要な原則をまとめたもので、以下のような原則が含まれています。
- S : 単一責務の原則
- O : 開放閉鎖の原則
- L : リスコフの置換原則
- I : インターフェース分離の原則
- D : 依存性逆転の原則
どれもとても重要な原則ですが、以前の記事で少し触れたかもしれませんが、洗練された情報というのは、応用できるまで飲み込むのに、時間と手間がかかるものです。
なので、今回は、結果的にどのように使われているか、という結果目線でのお話となってしまいました。
今回、前編・後編通して伝えたかったことは、2つあります。
一つは、オブジェクト指向というのは、ソースコードを効率化するレベルの話ではなく、ソフトウェアが実現するビジネスを、圧倒的に抽象化してモデル化を行うことができ、それにより変更に強いソフトウェアを構築することができる、ということです。
ビジネス目線からも、SOLIDの原則を眺めることで、業務的な責務の単一化や、業務的な外部との境界を定め、業務的な外部との接点(インターフェース)を決めたりと、原則を使用するときの判断軸が、プログラム目線×ビジネス目線となり、原則の有効性を実感しやすくなるというメリットがあります。
もう一つは、プログラムの先に見える、境界と公開のイメージを、皆さんにも持てるようになってほしい、という点です。
とにかく、Javaだったら、同じパッケージ内(フォルダ内)に、関係するオブジェクトを一緒に詰め込んで、全てパッケージ・プライベートにしたら、外部から一切その存在を認識することができなくなります。これで、外部と内部の境界が区切られたことと同義になります。
必要に応じて、公開APIをpublic
スコープで公開してください。そこが、その境界内の価値にアプローチする、唯一の管理された接点になります。
今回は前後編で長くなりましたが、初心者エンジニアの皆さんはついてこれましたかね?
重要なのは、プログラムの先に見えているものです。
それをなんとなくでも、感じ取ってもらえれば、この記事を書いた甲斐があります。
それでは、今回はこの辺で。