わかった気にならないカプセル化

オブジェクト指向の三要素のひとつ「カプセル化」について
わかっているのに、なぜか部品化が進まない。
修正箇所が広範囲になってしまうということがないでしょうか?
それ、カプセル化ができていないかもしれません。
目次
- はじめに
- カプセル化とは何か?
- 簡単ではない「情報隠蔽とカプセル化」の違いの説明
- 情報を隠蔽する
- 凝集度と結合度
- なぜカプセル化するのか
- 終わりに
はじめに
この記事は アイソルート Advent Calendar 2022 9日の記事です。
はじめまして。 sugimoto です。
前回は anan の 「迷えるエンジニアへ贈る、キャリア形成のために必要なたった一つのルール」です。
技術と同じくらい身近な内容ですが意識していない人もいると思います。
ぜひ読んで自分事に置き換えて振り返ってみてはどうでしょうか。
さて、今回はカプセル化についての話です。
なぜこの話を記事にしようかと思ったのは。
結論からいうと・・・
おまいらの作ったクラスは使いにくいんじゃ!
ということです。
何が使いにくいのか?どうすればいいのかを書く前に
そもそもカプセル化の理解が足りていないんじゃないだろうか?
と考えました。
カプセル化と情報隠蔽もどきを同じ扱いにしているwebの記事を見かけることもありますが、
そのような記事を訂正したいということでなく単純に
カプセル化を理解してもらうことを目的に
対象読者を
- カプセル化を知らない人
- オブジェクト指向なのに再利用がうまくできないと感じてる人
- プログラミングでバグを減らしたい人
としています。
カプセル化とは何か?
まずはカプセル化とは何か?
日本語wikiによると
|
ふむふむ情報隠蔽と同義らしい
|
データと操作をまとめることをカプセル化と呼ぶのか・・・
どっちなの!?
このどっちともとれるようなとられないような部分の認識が悲劇の始まりかもしれません。
簡単ではない「情報隠蔽とカプセル化」の違いの説明
カプセル化と情報隠蔽は同義なのでしょうか?
情報隠蔽とカプセル化の違いについては、10年以上前から
|
のように意見や主張が乱立していました。
他にも、blogのタイトルが「カプセル化は情報隠蔽ではない」とありながら
|
”著者はこの2つをほとんど区別せず、しばしば直接的に同じものだと主張する”
なんだってぇ!?
もちろん続きがあります。
|
私がこの記事で言いたいのは、これらが合っている間違っているとかを議論したいわけではありません。
カプセル化や情報隠蔽について沢山の定義があるなか、浅い知識のまま設計、コーディングをすすめるとオブジェクト指向の恩恵を受けづらいということです。
情報を隠蔽する
カプセル化について私が簡単に説明するとしたら
こちらの式で説明します。
|
少し具体的にコード(java)で説明します。
public class Car {
public int speed;
}
これだとspeedに自由に値が設定できてしまいます。
たとえばspeed負の値が設定が設定されたらどうなるでしょうか?(バックするのでしょうか?)
そのためspeedを外部から直接アクセスできないようにします。
public class Car {
private int speed;
public int getSpeed() { return speed; }
public void setSpeed(int speed) {
if(speed <= 0) this.speed = speed;
}
}
よく見かけるであろうカプセル化の説明ですが、
これって意味あるの?
と思ったことはないでしょうか。
もちろん負の値が設定できなくなることの意味は大きいですが
結局、speedを設定すること自体は変わりません。
実際の車を運転する時にsetSpeedなんて意識せずにアクセルを踏むんじゃないでしょうか。
これは情報が隠蔽できていないから起こることです。
speedを隠蔽したのに?
いったんspeedの隠蔽の話を置いておき、ちょっと話を進めます。
燃料(fuel)というフィールドを追加して、速度差によって燃料の減り具合を変更するような修正が追加されたとします。
speedが0のものを80にする場合と、speedが40のものを80にする場合にfuelに減りに差がでるということです。
このような時に例として以下のような実装が考えられます。(例としてだしたのであまり意味はないです)
int speedDiff = speed - this.spped;
if(speedDiff > 80) {
this.speed = speed;
fuel -= 30;
}
else if(speedDiff > 40) {
this.speed = speed;
fuel -= 15;
}
else if(speedDiff > 20) {
this.speed = speed;
fuel -= 10;
}
else {
this.speed = speed;
fuel -= 4;
}
if文が増えた場合、その部分のロジックをクラス化して多態性で解決するのがオブジェクト指向っぽいです。
そのロジックで、燃費の良い車や、スピード重視の燃費の悪い車など差をつける実装などが考えられます。
そのロジックの部分を関数クラスのような使い方にしたり、ロジッククラスとしても、どのロジックを設定するかなど
利用者(client)に、その使い方を把握させるようなことを実際によくみかけます。
図にすると、このような感じになります。

図1
データとアルゴリズムが分離しているため、データを加工・編集する方法を利用者が理解する必要があります。
いわゆる仕様を知らないと、そのクラスを利用できないということです。
※client自身がアルゴリズムを持つようなケースは身に覚えがある人も多いのではないでしょうか。
つまり・・・仕様を隠蔽できていないということです。
もちろん、仕様を把握しないで済むはずがないのですが、問題は「誰が仕様を把握すればよいか?」という部分です。
クラスを利用するプログラマでなく、そのクラス自身が知っておけばよいわけです。
※当たり前ですが、そのクラスを実装するプログラマは別です

図2
このように仕様を隠蔽することで利用者はspeedとfuelの関係性を意識することなく簡単にspeedを変更することが可能になります。
戻って、speedを隠蔽したのに情報隠蔽できていないとは、どういうことだろう?
もうおわかりかと思いますが、データ(speed)を隠蔽しただけで内部のデータ構造を意識した操作を公開してしまっては使い勝手はほとんど変わりません。
冒頭の式「カプセル化 = データ + 手続き + 外部インターフェース」で表すと
外部インターフェースとして必要なのが、アクセルとブレーキ、だけでよくなります。
※実際には内部構造を意識させないメソッドを用意すれば、インターフェースを定義しなくても十分だとは思います

図3
インターフェースを導入することで、clientは内部の実装について知る必要がなく速度を上げたり下げたりすることができます。
車だろうが、飛行機だろうが、乗馬の馬でさえ。
※飛行機も馬もアクセルとブレーキじゃないと思います
凝集度と結合度
さきほどの例のように、コードを集めたりインターフェースを切り出したりすることを
凝集度と結合度という用語で少し解説していきます。
凝集度
「凝集度」という言葉を聞いたことがあるでしょうか?
簡単にいうと同じことを実現するためのコードを一か所にまとめるということです。
尺度なので相関的に高い・低いと表現します。
データとアルゴリズムがバラバラになっていた場合、凝集度が低いといえます。
凝集度が低い場合の欠点には次のようなものがあります。
- モジュール群を理解することが難しくなる。
- システムの保守が難しくなる。ある論理的修正が複数のモジュールにまたがって影響する可能性が増すためである。
- モジュールの再利用が難しくなる。凝集度の低いモジュールの提供するAPIには一貫性がないことが多いため。
冒頭の、なぜ「おまいらの作ったクラスは使いにくいんじゃ!」かというと凝集度が低いからです。
この凝集度を高めることをなんというかは、この記事内ですでに書いています。
|
カプセル化ができていないクラス群は使いにくく再利用も難しいです。
ちなみに再利用の方法が単純にクラスを使うのでなく、ソースのここを見ながらコピペで一部を修正しなさい。
という場合(実際によく聞く)間違いなくカプセル化ができていません。
結合度
凝集度と同じタイミングで結合度というのも議論されやすいです。
図1のように3つのclient、データ、アルゴリズムがバラバラになっていて、どこかの修正が他の2つへ
影響がでるようなものを結合度が高いといいます。
図2や図3のように、関連が減ったものを図1に比べて結合度が低いといいます。
内部実装にかかわるAPIを直接使わないことで、実装の変更がclientには影響しなくなります。
凝集度を上げると関連が減るので結合度が低くなります。

図4
結合度については、もっと詳細があるのですが長くなるので今回は説明しません。
なぜカプセル化するのか?
ここまで順を追って説明したので、カプセル化についての理解は変わってきたのではないでしょうか
いきなりカプセル化とはこういうものだ!としても頭に入らなかったものが目指すものが、わかると理解しやすくなるかと思います。
こちらにはカプセル化して何をするかのガイドラインをまとめられています。
以下の観点でクラス設計(カプセル化)はできているでしょうか?
- 不正な操作からの保護
- 複雑さの隠蔽
- 部品化/再利用性の向上
- 修正/変更に対する影響範囲の極小化
- バグの影響範囲の極小化
終わりに
カプセル化の定義より目指す目的を理解することが大事だと考えます。
- 関連するコードをまとめて凝集度を高める
- 関連のあるモジュールを減らす
- 保守しやすくする
- データやアルゴリズムを隠蔽し使い方だけ公開する
- 不要な操作を公開しない(データ隠蔽やアクセッサの利用)
- 結合度を低くすることを目指す(実装でなくインターフェースを公開)
- 再利用性を高める(仕様を知らなくても使えるようにする)
- 複雑な使い方を簡単な操作として公開する
- 仕様変更による影響範囲をモジュール内に留める
スピードと品質はトレードオフとよく言われますが、ちょっとした手間を省いてテスト不具合の対応に時間を取られるより
モジュール化した品質を高める設計・実装をして不具合を発生させない取り組みをすることが大事じゃないでしょうか。
カプセル化を実践していけば少しづつですが改善していくと思います。
参考
カプセル化、情報隠蔽、データ隠蔽
https://bleis-tift.hatenablog.com/entry/20090201/1233426011
Encapsulation is not information hiding(カプセル化は情報隠蔽ではない)
https://www.infoworld.com/article/2075271/encapsulation-is-not-information-hiding.html
オブジェクト指向技術の基本概念(カプセル化)
https://www.ogis-ri.co.jp/otc/hiroba/technical/concept.html#capsulation
カプセル化と隠蔽
http://msugai.fc2web.com/java/capsulate.html
オブジェクト指向 カプセル化・ポリモーフィズム / OOP2
https://speakerdeck.com/nrslib/oop2
カプセル化と情報隠蔽の違いを簡単に分かりやすく説明
https://boukenki.info/kapuseruka-jouhouinnpei-chigai/