はじめに
オブジェクト指向はなんとなく理解しているつもりだったが、人に明確に説明できるほどではなかったため、オブジェクト指向でなぜつくるのかを読んで、まとめた。非常に分かりやすい書籍なのでQiitaをご覧いただき、いいねをつけていただいた上でご一読ください。
1. オブジェクト指向はソフトウェア開発を楽にする技術
オブジェクト指向とは、業務分析から要求定義、設計、プログラミング、開発プロセスまでをカバーするソフトウェア開発の総合的な技術である。
オブジェクト指向が難しいと思われるのは、技術そのものの複雑さに起因するのではなく、状況が混乱しているためである。そして分からないがゆえ、一般人には手の届かない神秘的な魔法だと思われてしまう。
混乱①用語の洪水
対象分野が広いゆえ、特殊用語のオンパレードになってしまい混乱してしまう。
現実世界やシステムは、データと手続きをまとめた「オブジェクト」から構成される。オブジェクト指向では、ソフトウェアは「クラス」として定義され、そこから「インスタンス」が生成されて動作する。システムは「インスタンス」が「メッセージ」をやりとりし合うことで動作するが、「カプセル化」されているため、内部の詳細は見えない。これを「情報隠蔽」と呼ぶ。
継承、汎用、特化、スーパークラス、サブクラス、インターフェース、多重継承、属性、関連、集約、委譲、オーバーライド、オーバーロード、アクセス制御、コンストラクタ、パッケージ、例外、ガーベージコレクション、フレームワーク、クラスライブラリ、コンポーネント、デザインパターン、ユースケース、モデリング、UML、リファクタリング、アジャイル開発プロセス、RUP、XP・・・
混乱②比喩の乱用
比喩が強烈な印象として残ってしまい、具体的な仕組みは聞く側の思い込みで間違って理解してしまう。
「動物がスーパクラスで、哺乳類や魚類がサブクラス。卵を産みミルクで子供を育てるカモノハシは、は虫類と哺乳類の多重継承に相当する」
「人は "誕生日" という属性を持つ。具体的な人である田中さんに "年齢を教えてください" というメッセージを送ると、 "28才です" と答えが返ってくる」
「病院で医者や看護師、薬剤師が連絡試合ながら仕事をするように、オブジェクトもコンピュータの中でメッセージを送り合いながら仕事をする」
混乱③なんでもオブジェクト指向
極端な抽象化によって、現実世界をそのままプログラムに表現できるという勘違いしてしまう。
オブジェクト指向を言葉通りに解釈すると、現実世界の人や組織、物事や出来事、コンピュータシステムの機能、システムが管理する情報、プログラムの構成要素など、なんでもオブジェクト(もの)であると説明できる。
2. オブジェクト指向と現実世界は大違い
クラス
クラスは種類(集合)。インスタンス(オブジェクト、要素)は具体的なもの。
現実世界では、先に具体的なもの(チワワやポメラニアン、ブルドッグ)があり、それをさまざまな基準で分類(犬)する。しかしオブジェクト指向の世界では、クラスを定義した上でインスタンスが作られる。
ポリモーフィズム
類似したクラスに対するメッセージの送り方を共通にする仕組み。泣けというメッセージを送ったとして、犬ならワン、赤ちゃんならオギャーと泣くように、相手が具体的にどのクラスのインスタンスであるかを意識せずにメッセージを送れる。
現実世界では、個人個人の意志を持って自由に動くので泣けと言われても泣かない。しかしオブジェクト指向の世界では、外部からの指示がない限り動くことはなく、あらかじめ決められたことをその通りにしか実行できない。
継承
ものの種類の共通点と相違点を体系的に整理する仕組み。動物というスーパークラス(全体集合)の中に、鳥類や哺乳類などサブクラス(部分集合)がある状態。動くや泣くは動物クラス、飛ぶや出産するは固有の性質なので、継承されたクラスに定義する。
現実世界では、赤ちゃんは大人になり、老人になる。しかしオブジェクト指向の世界では、作られたインスタンスは複数のクラスに帰属することができず、時間が経っても別のクラスに所属替えできない。
3. OOPを理解する近道はプログラミング言語の歴史にあり
機械語からアセンブリ言語、高級言語までの進化は、コンピュータにやらせたい仕事を、いかに簡単に、かつ人間に親しみやすい方法で表現するかを追求してきた。それからコンピュータに対する要求が膨らみ、毎回作り直していたのでは間に合わなくなっていった。つまりプログラムの寿命が想定よりも長くなったため、保守性と再利用性を重視に変化していった。
機械語
ごく限られたスーパープログラマだけがコンピュータを操れた。1940年代。
A10010
8B160210
01D0
A10410
アセンブリ言語
非効率なプログラムを改善するために、人間に分かりやすい記号に代替された。アセンブラという別のプログラムによってコンパイルして、機械語を生成する。余談だが、アセンブリ言語のコンパイラはアセンブリ言語であるが、すぐににわとりたまご話だと分かる。つまり最初のコンパイラは以前の言語で書かれている。つまりアセンブリ言語の最初のコンパイラは機械語であり、歴史は繋がっているのである。
MOV AX, X
MOV DX, Y
ADD AX, DX
MOV Z, AX
高級言語
より人間に親しみやすい表示形式にされた。FORTRAN、COBOL。1950、60年代。
Z = X + Y
構造化プログラミング(GOTOレスプログラミング)
NATO(北大西洋条約機構)の国際会議での、ソフトウェア危機(20世紀には世界の総人口がプログラマになっても、増大するソフトウェアの需要に追いつかない)に対応するために登場。
基本的な考え方は、正しく動作するプログラムを作成するためには、わかりやすい構造にすることが重要である。具体的には、GOTO文(プログラム内の任意のラベルに無条件で分岐する命令)の廃止して、ロジックを基本三構造(順次進行、条件分岐、繰り返し)だけで表現する。
1970年代当時はメモリ容量やCPU速度などハードウェア能力が貧弱だったことから、1バイトでも小さく、1ステップでも短いプログラムを書くこと(GOTO文)が推奨されていた。今日でも表現するスパゲッティコードの始まりである。そのため、プログラムサイズの増大や実行速度の遅延を招くという批判もあった。しかし時代とともにハードウェア能力が進歩したため、わかりやすいプログラムをつくることが重要な課題になっていった。
またサブルーチン(メソッド、プロシージャ、関数、副手続き)の独立性を高めることも保守を強くするために工夫された。1940年代からプログラムの複数の場所に現れる同じ命令をまとめて、プログラムサイズを小さくしてプログラム作成の手間を減らすことはされてきた。
しかしそれだけでは不十分であり、呼び出し側(メインルーチン)とサブルーチンで共有する情報(変数に格納されるデータ)を少なくすること必要がある。特に複数のサブルーチンが共有するプログラム全体からアクセスできる変数(グローバル変数)をいかに減らせるか。具体的には、サブルーチン内でのみ利用できるローカル変数(局所変数、自動変数)と引数の値渡しで解決する。
4. OOPは無駄を省いて整理整頓するプログラミング技術
構造化プログラミングによって、グローバル変数経由での情報の受け渡しを必要最小限に抑えることが可能になった。しかしローカル変数は、サブルーチンの呼び出しが終わると存在自体がなくなってしまう一時的な変数である。すなわち、グローバル変数として保持せざるを得ない課題がある。また構造化プログラミングで再利用できるのは、サブルーチンだけという貧弱なものだった。この2つの課題を解決するために、OOPが登場した。具体的には、OOPの三大要素(クラス(カプセル化含む)、ポリモーフィズム、継承)を利用する。
クラスは、関連性の強いサブルーチンとグローバル変数を1つにまとめて粒度の大きいソフトウェア部品をつくる仕組み。バラバラに存在していたサブルーチンと変数をまとめて整理できる。ポリモーフィズムと継承は、共通サブルーチンではうまく対処できない重複したコードを一本化する仕組み。ソースコードの無駄を徹底的に省くことができる。これらの仕組みを利用して汎用性の高い機能を切り出せば、複数のアプリケーションで再利用可能である。
クラス
まとめて、隠して、たくさんつくる
- サブルーチンと変数をまとめる
- 全体の部品の数を減らせる
- 責務が明確かつクラス内で重複しなければ良いため、サブルーチンの名前づけが楽になる(探しやすくなる)
- クラス内部だけで使う変数やサブルーチンを隠す(
public
やprivate
)- グローバル変数を使わずにすむ
- 1つのクラスからインスタンスをたくさんつくる
- クラスとして定義すると、実行時にいくつでもインスタンスをつくることができるため、同種の情報を複数同時に扱う処理であってもそのクラス内部のロジックをシンプルにできる
インスタンス変数は、長持ちするローカル変数(一旦インスタンス変数が作られたあとは、必要なくなるまでメモリ上に表示される)、仲間内のグローバル変数(別のクラスのメソッドからアクセスできないように隠すことができる)である。さらに、必要なだけつくることができる。
ローカル変数 | グローバル変数 | インスタンス変数 | |
---|---|---|---|
複数のサブルーチンからアクセス | 🙅♀️(できない) | 🙆♂️(できる) | 🙆♂️(できる) |
アクセス可能範囲の限定 | 🙆♂️(1つのサブルーチンからのみアクセスできる) | 🙅♀️(どこからでもアクセスできる) | 🙆♂️(同じクラス内のメソッドからのみアクセスできる) |
存在期間の長さ | 🙅♀️(サブルーチン呼び出し時に作られ、抜ける特に破棄される一時的なもの) | 🙆♂️(アプロケーション開始から終了まで) | 🙆♂️(インスタンスが作られてから必要なくなるまで) |
変数領域の複製 | 🙅♀️(1時点では1つしか作れない) | 🙅♀️(1変数につき1つしか作れない) | 🙆♂️(実行時にいくつも作れる) |
ポリモーフィズム
サブルーチンを呼び出す側のロジックを一本化する仕組み。すなわち、共通メインルーチンをつくる仕組み。共通サブルーチンは呼び出す側が増えても、呼び出される側を修正する必要がなく、共通メインルーチンは呼び出される側が増えても、呼び出す側を修正する必要がない。
継承
クラス定義の共通部分を別クラスにまとめることで、コードの重複を排除する仕組み。継承されたサブクラスはスーパクラスの変数とサブルーチンを利用できる。
三大要素 | クラス | ポリモーフィズム | 継承 |
---|---|---|---|
説明 | サブルーチンと変数をまとめてソフトウェア部品をつくる | メソッドを呼び出す側を共通化する | 重複するクラス定義を共通化する |
目的 | 整理整頓 | 無駄を省く | 無駄を省く |
覚え方 | まとめて、隠して、たくさんつくる | 共通メインルーチンをつくる | クラスの共通部分を別クラスのまとめる |
その他
- 型にはめることで楽をできる。メモリ領域を有効に利用できるため。またプログラムのエラーを未然に防げる。
- GOTO文や明示的なポインタの使用、共用体、グローバル変数など不要な機能を使えなくするというある種退化している。
- さらに進化もしている。
- パッケージ(まとめたクラスをさらにまとめる、名前付の法則がある
jp.co.nikkeibp
) - 例外(戻り値とは異なる形式で、メソッドから特別なエラーを返す)
- ガーベージコレクション(不要になったインスタンスをメモリ上から削除する)
- パッケージ(まとめたクラスをさらにまとめる、名前付の法則がある
5. メモリの仕組みの理解はプログラマのたしなみ
プログラムの実行方式
- コンパイラ方式(コードを機械語にコンパイラで一括変換)
- 実行効率が良い
- 実行するまで手間
- 実行したあとにエラーが判明
- 銀行システム
- インタプリタ方式(コードを機械語に逐次変換)
- 手軽に実行できる
- 異なるマシンやOSで互換性を保てる
- 実行速度が遅い
- Web
- 中間コード方式(2つの良いところ取り)
- 実行効率が良い
- 異なるマシンやOSで互換性を保てる
スレッド
- プログラムの実行単位(プロセスよりも小さい)
- マルチスレッド形式
- 基本的にCPUはスレッドを1つずつしか処理できない
- 無駄な待ち時間を減らして全体の処理効率を向上させる
メモリの使い方
種類 | 静的領域(メソッドエリア) | ヒープ領域 | スタック領域 |
---|---|---|---|
使われ方 | アプリケーション開始時に固定確保される | 開始時に一定領域が確保され、必要の都度アプリケーションに動的に割り当てられる | LIFO(後入れ先出し方式) |
格納される情報 | グローバル変数、実行コード | 任意(アプリケーションによる) | 呼び出したサブルーチンの引数、ローカル変数 |
確保される単位 | アプリケーションでまとめて1つ | システムまたはアプリケーションで1つ | スレッドごとに1つ |
- クラス情報はクラスにつき1つだけロードされる
- OOPで書かれたプログラムは、有限のメモリ領域であるヒープ領域をじゃぶじゃぶ使って動く
- インスタンスを格納する変数にはインスタンスそのものではなく、インスタンスのポインタ(場所を示す情報)が格納される
- インスタンスを格納する変数を他の変数に代入した場合、ポインタがコピーされるだけで、ヒープ領域にあるインスタンスそのものは変化しない
- ポリモーフィズムは異なるクラスが同じ顔を見せる
- 継承される情報の種類によってメモリ配置は異なる
- 孤立したインスタンスはガベージコレクタが処分する
6. OOPがもたらしたソフトウェアとアイデアの再利用
(1)最初にOOPを利用して再利用部品群をつくり、(2)次に再利用部品群に共通して現れる設計のアイデアを抽出したデザインパターンが登場し、(3)最後は反対に再利用部品群を作るためにデザインパターンを利用するようになった。(2)と(3)が循環して、相互に発展していった。
再利用部品群
クラスライブラリ
- 汎用的な機能を持つクラスをたくさん集めたもの
- 言語仕様の一部
- 従来のプログラミング言語では、関数ライブラリ(再利用できる部品はサブルーチンだけ)であった
- (クラス)ライブラリ中のクラスからインスタンスを作成して、メソッドと変数定義をまとめて利用する
- (ポリモーフィズム)ライブラリから呼び出される側のロジックをアプリケーション固有の処理で置き換える
- (継承)ライブラリ中のクラスに、メソッドや変数を追加定義して新しいクラスを作成する
フレームワーク
- クラスライブラリと似ているが、特定の目的を果たすためのアプリケーションの半完成品のこと
- 基本的な制御の流れをあらかじめ用意し、アプリケーションで個別の処理を組み込むようにしたソフトウェア部品群
- 基本的な処理はフレームワーク側で用意して、アプリケーション固有の処理(継承を利用してあらかじめ用意)は、ポリモーフィズムを利用して呼び出す
- ハリウッドの法則(Don't call us, we will call you.)
コンポーネント
- OOPのクラスよりも粒度が大きい
- ソースコード形式ではなく、バイナリ形式として提供される
- コンポーネントの定義情報を含めて提供される
- 機能的に独立性が高く、内部の詳細を知らなくても利用できる
デザインパターン
- 設計の定石集
- GoFの23デザインパターン(デザインパターン | TECHSCORE(テックスコア))
- 様々な分野に広がるパターン技術
名称 | 説明 |
---|---|
その他のデザインパターン | 特定の実装環境や用途に限定した設計のノウハウ集(J2EEパターン、EJBパターン、CORBAパターン、マルチスレッド向けのデザインパターン) |
アナリシスパターン | 業務分析や要求定義段階でつくられる、アプリケーションの問題領域を表現するパターン |
アーキテクチャパターン | ソフトウェアの全体的な構造を表現するためのパターン |
イディオム | JavaやC++などの特定のプログラミング言語を使いこなすためのテクニック |
リファクタリング | 既存のプログラムの機能を変更せずに、内部構造を改善するテクニック |
プロセスパターン | システム開発の進め方に関するパターン |
アンチパターン | システム開発で陥りがちな落とし穴と、それを回避するための方策をまとめたべからず集 |
7. 汎用の整理術に化けたオブジェクト指向
コンピュータは現実世界の一部だけを肩代わりするため、プログラミングの前段階で上流工程(業務分析や要求定義)が必要。上流工程は現実世界の話なので、OOPの仕組みを応用して使う(汎用の整理術)。下流工程は、プログラミング技術。
クラスとインスタンスは集合論に応用
- 加藤さんと山田さん(固有名詞)がインスタンスで、従業員(一般名詞)がクラス
- 顧客をさらに個人顧客と法人顧客に分類すること
- 人間を男女に分類すること
メッセージパッシングは役割分担に応用
- レストランでウェイトレスに料理を注文すること
- 建設会社のA社に工事を発注すること
- ポチにお手と命令すること
8. UML(統一モデリング言語)は形のないソフトウェアを見る道具
名前に言語とついているが、ソフトウェアの機能や内部構造を表現する図の書き方のこと。自然言語(曖昧)とコンピュータ言語(厳格)の欠点を補うため言語とされている。13種類のダイアグラムがある。
使い方①プログラム構造や動作を表現する
- クラス図(クラスの定義情報とクラス間の関係を表現)
- シーケンス図(実行時のインスタンス間のメソッド呼び出しを時系列で表現)
- コミュニケーション図(実行時のインスタンス間のメソッド呼び出しをインスタンスの関係中心で表現)
使い方②汎用の整理術の成果物を表現する
- クラス図(集合論で分類・整理された現実世界の物事の関係を表現)
- シーケンス図(役割分担された人や組織が協調して全体の仕事を達成する様子を時系列に表現)
- コミュニケーション図(役割分担された人や組織が協調して全体の仕事を達成する様子を構造中心に表現)
使い方③非オブジェクト指向を表現する
- ユースケース図(コンピュータに任せる仕事の範囲を表現)
- アクティビティ図(現実世界の仕事の流れを表現)
- ステートマシン図(外部からのイベントによる状態変化を表現)
9. 現実世界とソフトウェアのギャップを埋めるモデリング
モデリングの目的
- 業務分析:現実世界様子をそのまま捉える
- 要求定義:コンピュータの性質を考慮して、肩代わりさせる仕事の範囲を決める
- 設計:ハードウェアの能力、OSやミドルウェアの特性、プログラミング言語の表現能力などを考慮して、ソフトウェアの構造を決める
10. 擬人化して役割分担させるオブジェクト指向設計
- このクラスはこの情報を知っているが、その情報は知らないので、その機能を持たせることはできない
- こいつはこういう役割だから、このメソッドを呼ばせるようにしよう
実行効率よりも保守性や再利用性が重視される時代に、求められる設計
- 重複を排除する
- 部品の独立性を高める(凝集性を強めて、結合度を弱める)
- わかりやすい命名
- 秘密をたくさんつくる(隠す仕組みを利用)
- 小さくつくる
- 依存関係を循環させない
11. オブジェクト指向から生まれた柔軟な開発プロセス
ウォーターフォール
- 要求定義->設計->プログラミング->テスト プロセスごとにレビュー
- 開発環境が今よりもずっと貧弱だった時代は、ソフトウェアの変更コストは高かった
- ソフトウェアの変更には大きなコストがかかることを前提に、作業の後戻りを極力回避する
- 課題①要求段階でMECEにするのは不可能であり、途中でシステムの前提条件や目標が変わってしまうこともある
- 課題②プログラミングやテストが後半部分なので、技術的問題に気づくのが遅くなる
反復型
- 要求定義->設計->プログラミング->テスト を繰り返す ことで2つの課題を解決
- Rup(ラショナル統一プロセス)
- 指針やノウハウを提供してくれる。しかしカスタマイズ前提にしているため、手順通りに進めるだけでシステムが完成する作業マニュアルではない。
- XP(eXtream Programming)
- メンバーのモチベーションが上がり、動くソフトウェアを手早くつくれるかもしれない。しかし全体の目標に立てる視点が欠けているため、決められた納期に一定の予算でソフトウェアを開発する受託開発ビジネスに本質的に適応しない。
アジャイル
- プロセスやツールよりも、個人、そして協力を
- 完全なドキュメントよりも、動くソフトウェアを
- 顧客と契約交渉よりも、顧客との協調を
- 計画に従うよりも、変化変の対応を
12. オブジェクト指向を使いこなそう
- OOPを生かすも殺すもプログラマ次第
- OOPの利用自体を目的にしない