0. はじめに
名作の多い「なぜシリーズ」の書籍のひとつである、オブジェクト指向でなぜつくるのかを読みましたので、参考になった点や教育に使いたい点をまとめてみました。
1. オブジェクト指向でなぜつくる?
書籍のタイトルにもなっている疑問。
- プログラムの再利用性を高めるため。
- 変更に対して柔軟に対応するため。
- 理解しやすいプログラムにするため。...etc
色々ありますが、少し嚙み砕くとソフトウェアを楽に作るためと言えます。
2. オブジェクト指向が難しいと言われているのはなぜ?
オブジェクト指向は難しく、とっつきにくいと言われることがあります。その理由としては、以下のような要因が挙げられます。
2-1. 用語の洪水
「継承、スーパークラス、インターフェース、属性、オーバライド・・・」
たくさんの用語があるため、最初は覚えるのが大変になります。
しかし、オブジェクト指向は広い領域をカバーしているため、仕方ないことと言えます。寧ろ覚えないとまずいらしいです。
2-2. 比喩の乱用
「動物がスーパークラスで、犬がサブクラスで・・・」
「人オブジェクトに、年齢教えてくださいとメッセージを送って・・・」
といった例はよく見かけるかと思います。
しかし、比喩が強烈な印象として残ってしまい、実際の仕組みと学ぶ側の思い込みで間違って解釈されてしまいます。
2-3. なんでもオブジェクト症候群
「人間、会社、コンピュータ、自動販売機、ペットボトル、イベント・・・」
この世に存在する以上全てモノ(オブジェクト)であると表現できます。
このような極端な抽象化により、現実世界をそのままプログラムに表現できるという勘違いが生まれてしまいます。
3. オブジェクト指向と現実世界は別物
オブジェクト指向は「現実世界をそのままソフトウェアに表現するもの」と捉えられることがあります。
その考えが、返って混乱を招いてしまいます。オブジェクト指向と現実世界は似て非なるものです。
3-1. モノはクラスから作られない
-
オブジェクト指向の場合
インスタンスを作るための仕組みとしてクラスがあります。
また、インスタンスが帰属するクラスは1つです。 -
現実世界の場合
先に具体的なインスタンス(ex.人間)があり、それを見る側の違いによって分類(クラス)します。
会社に行けば会社員、家に帰れば父親、といった具合です。
3-2. 現実世界ではメッセージだけでは行動しない
-
オブジェクト指向の場合
「鳴け!」とメッセージがきたら「ニャー」「ワン」と鳴きます。オブジェクト指向では命令には逆らいません。 -
現実世界の場合
「鳴け!」とメッセージがきたら「ニャー」「ワン」と鳴く場合もありますが、無視することもあります。噛みつく場合もあるかもしれません。現実世界では命令に逆いまくりです。
ソフトウェアは現実世界を表現するのではなく、人間の仕事の一部をカバーするためにあります。
コンピュータが得意な**「覚えること」「決まった仕事」**を、コンピュータに任せちゃおうぜってことです。
4. オブジェクト指向ができるまでの歴史
プログラミング技術は何度も工夫、改良がされ、オブジェクト指向が誕生しました。
プログラミング技術の進化の歴史は以下のようになっています。
4-1. 機械語
コンピュータは機械語しか理解できません。黎明期には人間が機械語でプログラムを書いていました。
A10010
8B160210
01D0
A10410
4-2. アセンブリ言語
機械語を少し分かりやすくするために、アセンブリ言語が登場しました。アセンブリ言語では、機械語を人間に分かりやすい記号に置き換えて表現します。
MOV AX, X
MOV DX, Y
ADD AX, DX
MOV Z, AX
4-3. 高水準言語
さらに人間に親しみやすい表現にするために、高水準言語が登場しました。高水準言語の登場により、プログラミングの生産性や品質は大きく向上しました。
しかし、それ以上にコンピュータの普及が爆発的に進んだため、ニーズは収まりませんでした。これはソフトウェア危機と呼ばれました。
Z = X + Y
4-4. 構造化プログラミング
ソフトウェア危機に対応するために、オランダのダイクストラ氏によって構造化プログラミングが登場しました。
構造化プログラミングの考え方は「正しく動作するプログラムを作成するためには、分かりやすい構造にすることが重要である」というものです。分かりやすくするために、具体的には以下のような方法が挙げられました。
-
GOTO文を廃止
ロジックを逐次進行、条件分岐、繰り返しの3つの構造だけで表現します。このことから構造化プログラミング言語はGOTOレスプログラミング言語とも呼ばれます。 -
サブルーチンの独立性を高める
サブルーチン自体はすでに発明されていましたが、サブルーチンを利用する際はグローバル変数を使って値を受け渡しすることが一般的でした。グローバル変数はプログラムの保守性やデバッガビリティを低下させます。
この問題に対応し、サブルーチンをもっと汎用的に利用するために、ローカル変数と引数の値渡しの仕組みが考案されました。
構造化プログラミング言語はプログラマにとって常識になりましたが、構造化プログラミング言語では解決できない2つの問題が残りました。
-
グローバル変数
ローカル変数はサブルーチンの呼び出しが終わると消えてしまいます。サブルーチンの実行機関を超えて保持する必要のあるデータはグローバル変数で扱わなければなりません。 -
貧弱な再利用
計算や文字列処置など基本的な処理(サブルーチン)については再利用可能でしたが、それだけでは増大するアプリケーションの規模からすると微々たるものでした。
5. オブジェクト指向の登場
グローバル変数や貧弱な再利用の問題に対応するために、オブジェクト指向が登場しました。
オブジェクト指向では、これまでにない3つの仕組み「クラス」「ポリモーフィズム」「継承」が追加されました。
5-1. クラス
5-1-1. まとめる
結びつきの強い変数やサブルーチンを1つのクラスにまとめる仕組みです。
この仕組みにより、1つのソースコード(部品)の行数を減らすことができます。
また、クラスとして1つのグループにまとめられているため、サブルーチンの名前付けが楽になります。
<オブジェクト指向より前の場合>
・TextFileOpen()
・TextFileClose()
・TextFileRead()
・JsonFileOpen()
・JsonFileClose()
・JsonFileRead()
<オブジェクト指向の場合>
■ Txetクラス
・FileOpen()
・FileClose()
・FileRead()
■ Jsonクラス
・FileOpen()
・FileClose()
・FileRead()
5-1-2. 隠す
クラスに定義した変数やサブルーチンを他のクラスから隠す(private)仕組みです。
隠すことより、外部から意図しない使われ方をすることを防ぎます。
5-1-3. たくさん作る
クラスを定義しておくことで、そこからいくつでもインスタンスを作ることができる仕組みです。
利用する側は、インスタンスを指定してサブルーチンを呼び出すため、どのインスタンスを処理対象とするかを特定できます。
5-2. ポリモーフィズム
ポリモーフィズムは、「色々な形に変わる」という意味になります。
サブルーチンは呼び出される側のロジックを共通化する仕組みですが、ポリモーフィズムは呼び出す側を共通化する仕組みになります。
具体的には以下の例で見てみましょう。ポリモーフィズムの仕組みを使えば、Human
であってもMonster
であってもGetNameCount()
やSpecialAttack()
が使えます。また、新しい生き物が増えた場合もこれらのメソッドは一切修正する必要はありません。
// 名前をカウントする
public int GetNameCount(Creature creature)
{
return creature.GetName().Length;
}
// 全員スペシャルアタック!
public void AllSpecialAttack(IEnumerable<Creature> creatures)
{
foreach (var creature in creatures)
{
creature.SpecialAttack();
}
}
5-3. 継承
(ポリモーフィズムの例でもちらっと出ちゃいましたが)似た者同士のクラスの共通点をまとめて別のクラスを作る仕組みです。つまり、コードの重複を排除することができます。
6. さらに進化したオブジェクト指向
JavaやC#などのプログラミングでは、さらに進化した機能ができました。代表的なものが「パッケージ」「例外」「ガベージコレクション」です。
6-1. パッケージ
クラスをまとめる仕組みで、フォルダのようなものです。パッケージを入れ子にして、階層構造を作ることも可能です。
6-2. 例外
戻り値とは違う形で、メソッドから特別なエラーを返す仕組みです。
「ネットワーク障害」「ディスクアクセス障害」「DBデッドロック」などで例外が使われることが多いです。
従来はエラーコードを使う方法が一般的でしたが、以下のような問題がありました。
- エラーコードの判定処理をアプリケーションで必ず行う必要がある。判定処理を書き忘れると、バグ発生時の原因特定が困難になる。
- エラーコードの判定処理が散在してしまい、冗長なプログラムになってしまう。
例外の場合、例外の後処理(catch)を書き忘れた場合は実行時エラーになります。
また、後処理が必要のないメソッドでは、例外が発生する旨の宣言だけをしておくだけでよくなります。
例外の仕組みには、無駄を省くことと間違いを防止することの2つの効果があります。
6-3. ガベージコレクション
自動的に不要なインスタンスを削除し、メモリを解放してくれる仕組みです。JavaやC#に備わっています。
ガベージコレクションがない場合は、明示的にメモリを解放する必要があります。
誤って必要なインスタンスを削除してしまうとバグに繋がりますし、削除し忘れるとメモリリークに繋がります。
ガベージコレクションはこの辺のお助けをしてくれます。
7. オブジェクト指向がもらたした再利用
オブジェクト指向は2つの再利用技術をもたらしました。それが「ソフトウェアの再利用」と「アイデアの再利用」です。
7-1. ソフトウェアの再利用
7-1-1. ライブラリ
汎用的な機能をもつクラスを集めたものです。従来のプログラミング言語では、再利用できる部品はサブルーチンだけでしたが、オブジェクト指向により以下が可能になりました。
- ライブラリのクラスからインスタンスを作成し、メソッドや変数を利用する。・・・クラス
- ライブラリから呼び出される側のロジックをアプリケーション固有の処理で置き換える。・・・ポリモーフィズム
- ライブラリのクラスから新しいクラスを作成し、メソッドや変数を追加する。・・・継承
7-1-2. フレームワーク
アプリケーションの半完成品のイメージになります。ライブラリとは逆に、フレームワークからアプリケーションを呼び出すように使用します。基本的な流れはフレームワークで提供し、アプリケーションで固有の処理を組み込みます。
7-2. アイデアの再利用
7-2-1. デザインパターン
優れた設計のアイデアをまとめたもとです。エリック(エーリヒ)・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディスによって作成されました。彼らはGoFと呼ばれ、彼らが出した23のデザインパターンはGoFのデザインパターンと呼ばれました。GoFのデザインパターンの詳細については以下をご覧ください。
8. UML
UMLはUnified Modeling Languageの略で、「統一モデリング言語」と訳されます。
グラディ・ブーチ、ジェームズ・ランボー、イヴァー・ヤコブソンによって作成されました。この3名は親しみをこめてスリーアミーゴと呼ばれています。
UMLは以下のように使われます。
8-1. UMLの使われ方
8-1-1. オブジェクト指向を表現
UMLはオブジェクト指向のプログラム構造や動作を表現するために使われます。
2次元の図を視覚で取り込むため、人間の頭脳に適しています。
つまり、UMLは形のないソフトウェアを見るための道具になります。
代表的な図としては、クラス図、シーケンス図、コミュニケーション図などがあります。※各図の詳細な説明はここでは割愛します。
ソフトウェア開発では、設計などで使われます。
8-1-2. 非オブジェクト指向を表現
オブジェクト指向で表現できない情報もUMLで表現します。
代表的な図としては、ユースケース図、アクティビティ図、ステートマシン図などがあります。※各図の詳細な説明はここでは割愛します。
ソフトウェア開発では、業務分析(現実世界の仕事を整理)や要件定義(コンピュータに任せる仕事の範囲を決める)などで使われます。
8-2. UMLはコミュニケーションのお助けをする
自然言語は人間同士のコミュニケーションに使われ、コンピュータ言語は人間がコンピュータに命令を与えるために使われます。
自然言語やコンピュータ言語は文字を使って表現するため、表現が複雑になったり、量が膨大になったりします。
一方でUMLは図を使って表現するため直感的に理解できるようになります。UMLは自然言語とコンピュータ言語の欠点を補うための言語とも言えます。
そのため、UMLで全てを表現しようとせず、コミュニケーションを助ける手段として気軽に使いましょう!
9. オブジェクト指向設計のコツ
保守性に強く、再利用しやすいソフトウェアを作るためのポイントは以下のようなものがあります。
9-1. 重複を排除する
重複があるほど、プログラムの理解・修正・テストなどが大変になります。
重複があるとコピー&ペーストが多くなる傾向があります。つまり、コピペが多い場合は注意が必要です。
設計の段階で重複がおきないように配慮すべきです。
9-2. 部品の独立性を高める
複雑なものを分かりやすくするコツは「分割すること」です。
独立性が高いと、部品の機能がハッキリするため、理解しやすくなります。独立性を高めるための考え方は以下です。
-
凝集度
機能のまとまり度合いを示す。強いほど良い設計。 -
結合度
部品間の結びつき度合いを示す。弱いほど良い設計。
独立性を高めるためのコツは以下のようなものがあります。
- クラス名やメソッド名を、分かりやすい名前にする。
- クラスが外部に公開する情報を最小限にする。
- クラスやメソッドを小さく作る。
9-3. 依存関係を循環させない
依存関係とは、ある部品が別の部品を利用していることを指します。
以下の例では、どれも単独コンパイルができません。また、どれかに手を加えた場合、他のComponent全てを確認する必要があります。
以下の例は、「Component C」は単独で利用できます。また、「Component A」を使用した場合は他のComponentの確認は不要です。
10. おわりに
私はプロジェクトを通してオブジェクト指向を学んだタイプの人間ため、この書籍により基礎などが整理できました。
書籍では、メモリや開発プロセスについても分かりやすく記載されていますが、別途専用の記事にしたいと思い、ここでは割愛させて頂きました。
オブジェクト指向に興味のある方は、是非書籍の方もご覧ください。
また、同じなぜシリーズの以下の記事も合わせてご覧頂けますと幸いです。