オブジェクト志向のカプセル化を、総合医療病院で例えると「窓口」という説を語ってみる。

2020/04/10

エンジニアの平山です。
以前MVCについて記事を書かせていただきましたが、今回は「オブジェクト指向」について私の経験や考え方をお伝えしたいと思います。
まずはMVCとも関連があるカプセル化について私の考えを書きます。

オブジェクト指向プログラミングの誕生背景

説明に入る前にそもそもオブジェクト指向プログラミングが誕生した背景について触れておきます。
例のごとくWikiから引用します。

オブジェクト指向プログラミングという考え方が生まれた背景には、計算機の性能向上によって従来より大規模なソフトウェアが書かれるようになってきたということが挙げられる。大規模なソフトウェアが書かれコードも複雑化してゆくにつれ、ソフトウェア開発コストが上昇し、~

気気付いた方もいるかもしれませんが、以前記事にした「MVC」の誕生背景と少し似ています。
「MVC」は複雑化するGUIでのシステム開発に対する対策、「オブジェクト指向プログラミング」は大規模化、複雑化するシステム開発に対する対策として誕生し、広まっていきました。
つまり、どちらも目指すのは「開発効率の向上」や「メンテナンス性の向上」であり、それによって得られる「下流フェーズ及び運用保守コストの削減」です。
オブジェクト指向プログラミングの3大要素と言われる「継承」、「カプセル化」、「ポリモーフィズム」もすべて同じ目的です。
今回はこの中の「カプセル化」について、実際にどのように使うのかをお伝えします。

カプセル化は「窓口」の設置

皆さんはいろいろな施設(病院やイベント会場など)に行くと最初に窓口に向かい、受付を済ませますよね?
そこで説明を受け、実際自分が行くべき所を確認し向かうと思います。
では、窓口がないとどうなるでしょうか?
施設内を歩き、自分が行くべき場所を自分で探すことになります。
慣れていない人は探すだけで時間がかかってしまい、「不便だな」と思うかもしれません。

システム開発の現場でも同じようなことが起こります。
例えば「共通部品」です。
「共通部品」はその名のとおり様々な処理で共通に使われるため、「どうやって使ったらよいかわからない」という不便なものだと、非常に使いづらく、開発の生産性に大きく影響してしまいます。
そのため、「共通部品」は便利なものでなければなりません。
さらに「共通部品」の修正がシステム全体に与える影響も大きくなるため、「共通部品」自体の開発・保守は非常に大変です。
あまり深く考えず他からプログラムを「流用」したものを共通部品にするとその時はよくても後々ひどいことになります。

カプセル化はそういった「利便性の向上」と「修正時の影響を抑える」効果があります。
よく使う便利なオープンソースのライブラリなども「カプセル化」されていないものはありません。
使いやすく、利用する側への影響が最小限になるような「窓口」を設置することが重要です。

カプセル化は「設計が命」

カプセル化の一般的な説明では
データ(属性)とメソッド(手続き)を一つのオブジェクトにまとめ、その内容を隠蔽すること。
と言われています。
この説明のとおり、外部に公開する情報を制限し、オブジェクトの使い方を限定させる目的で使われます。
これにより「利便性の向上」と「修正時の影響を抑える」というメリットが生まれます。
例としてよくある「ファイル操作」を使って具体的なメリットを説明します。

「ファイル操作」は概ね以下の流れで処理が行われますが、これらの処理を個別に実装せずにカプセル化されたオブジェクトを用意することを考えます。
・ファイルを開く(Open)
・ファイルに書き込む(Write)または読み込む(Read)
・ファイルを閉じる(Close)

カプセル化(窓口の設置)を行う上で重要なのが「利用シーン」を想定しておくことです。
例えば「1回の書き込み」、「複数回の書き込み」、「1行の読み込み」、「すべての読み込み」などです。
「1回の書き込み」・・・Open – Write – Closeまでを行う処理を用意
「複数回の書き込み」・・・Open、Write、Closeをそれぞれ個別に行う処理を用意
「1行の読み込み」・・・Open – Read(1行) – Closeまでを行う処理を用意
「すべての読み込み」・・・Open – Read(すべて) – Closeまでを行う処理を用意

この想定をどう考えるかで処理をどのように分けて、何を公開するのかが決まり、利便性や修正への影響が大きく変わります。
加えて「複数回の読み込み(1行ずつ)」などの機能が必要になった場合でも柔軟に対応できる必要があります。

さらに個別に実装した場合によく発生する以下のような問題にも対応する必要があります。
・エンコード(文字コード)が統一されていない
・排他制御(他の処理で使っている場合の対応)が行われていない
・エラー処理が統一されていない

こういった「動きの統一」という問題は比較的下流フェーズ(結合テストなど)で発生することが多く、影響も大きくなります。
これらの問題も考慮しカプセル化を行うことで問題の発生、修正時の影響を抑えることができます。
エンコード・・・指定有無
排他制御・・・待機かエラーか
エラー処理・・・Exception(上位へスロー)か戻り値か

また、こういったある種の「ルール」を設けることで、利用する側への注意喚起(統一性を考えて実装させる)という効果もあります。

このように一言に「カプセル化」といっても考慮すべき点が多く簡単に作れるものでもありません。
「カプセル化」を最大限に使うには設計技術が必要です。
皆さんの開発現場でもカプセル化が必須である「共通部品」などは比較的技術力のある方が作っているのではないでしょうか。

カプセル化は「突貫工事」も防止できる

これまで「共通部品」を中心にカプセル化のメリットを説明してきましたが、画面処理などのメイン機能でもカプセル化は必要になります。

よくある例として機能同士が連携するような仕様を考えます。
A画面の登録処理からB画面の更新処理を使うことや、A画面からB画面を呼び出し、その結果をA画面に反映するといったことも少なからずあり、後になってから必要になるケースもあります。
こういった場面でもカプセル化されているのといないのとで大きな差が出ます。

例えばA画面の登録処理からB画面の更新処理を使う場合でカプセル化されてないと以下のような状況が起こります。
・B画面の更新処理をやるにはB画面のフォームオブジェクトが必要になってしまっている。
・B画面の更新処理の前のチェック処理(一部)を行うことが前提となってしまっている。

この状況で行われる対応として、私の経験上だと以下のような対応を行うことが多いです。
・A画面の登録処理内でB画面のフォームオブジェクトを生成し、必要な情報を詰め替える
・B画面のチェック処理から必要な部分を抜き出し、A画面の登録処理内に書く
これらはいわゆる「突貫工事」です。
さらに他の画面からも同じように使いたいとなったらさらに「突貫工事」が進み、プログラムがどんどん複雑になり、メンテナンス性も低くなっていきます。

カプセル化を行っていればこういった場合にも必要最小限の対応で行えます。

カプセル化の範囲に壁はない

カプセル化を組み込む上で重要なのが役割分担(MVCの考え方)です。
役割分担ができていないと、カプセル化してもあまり意味がないといってもよいでしょう。

カプセル化は明確な役割を持ち、その役割を果たすために何が必要かが明示された使いやすい「窓口」を設けるというのがポイントです。

このカプセル化のポイントをプログラムで言い換えると以下のようになります。
・1オブジェクト(クラス)には明確な役割を持たせる
・使いやすく、持続性のあるパブリックメソッド(インターフェース)を設ける
・使う上で必要なプロパティ、パラメーターを明示する

このままですと抽象的なので、実装レベルでの例を挙げます。
・各画面の処理(メソッド)は適切な単位で分けておく(初期処理、入力チェック処理、DB登録処理など)
・各処理のパラメーターを「個別指定」と「画面のフォームオブジェクト」で分けたインターフェースを用意しておく(オーバーロードと言います)、または各処理専用のパラメーターオブジェクトを用意しておく
・不要なものをパブリックにしないようにして、保守期間中(大体5年間)はインターフェース仕様を継続することを想定しておく
・各処理で必要な情報はパラメーターまたは処理内で取得できるものにする(事前に行う前提処理をなくす)

こうしておくことで他の機能からも便利に使えるようになり、もし後になってから連携仕様が発生しても比較的スマートに修正できます。

こういった役割分担(オブジェクト、メソッド分け)と使い方(パブリックメソッド、パラメーターの定義)という部分はオブジェクト指向言語だけの話ではなく、ほとんどのプログラミング言語でも必要になる技術です。
また、その単位は末端のオブジェクト単体だけなく、いくつもオブジェクトを使う機能においても使えるものです。
オブジェクト指向の模範とされるデザインパターンにもFacade(複数のオブジェクトをまとめた窓口を作る)というパターンがあります。
参考:Facadeパターン

シンプルで使いやすく修正しやすい(カプセル化された)スマートなプログラムを書きましょう。

この記事を書いた人について

平山 和昭
平山 和昭
オーシャン・アンド・パートナーズ株式会社 システムエンジニア

顧客をリードし、最適なシステムを構築、提供できる技術者を目指しています。
コラムでは若手技術者向けを中心に今までの経験を踏まえた実際の開発現場で役立つ情報を発信していきます。
プライベートでは家族の足(専属運転手)となり、今ではかねてからやりたかった日々の送り迎えが日課になっています。