読書記録「オブジェクト指向でなぜつくるのか 第2版」(プログラミング技術編)

「第1章 オブジェクト指向はソフトウェア開発を楽にする技術」の読書記録

・オブジェクト指向の目的はソフトウェアの保守と再利用をしやすくすること

・オブジェクト指向を利用してソフトウェア構造を図式表現する方法をまとめたのがUML(統一モデリング言語)

「第2章 オブジェクト指向と現実世界は似て非なるもの」の読書記録

・クラス、ポリモーフィズム、継承などの概念を比喩で理解しないで、プログラムの仕組みとして理解する

・クラスは変数とサブルーチンをひとまとめにして独立性を保つ仕組み。
サブルーチンとは関数、プロシージャなどとも呼ばれ、繰り返し使われる命令の並びをまとめたもの

・ポリモーフィズムと継承はコードの重複を避ける仕組み

・インスタンスは実行時にインスタンス変数をヒープ領域に展開したもの

「第3章 OOPを理解する近道はプログラミング言語の歴史にあり」の読書記録

・【プログラミング言語の歴史】
プログラミング言語は、機械語→アセンブリ言語→高級言語と発展していった。

1960年代後半NATOで「ソフトウェア危機」として、増大するソフトウェア開発の需要に対して供給能力が追いつかない状況が危惧された

ソフトウェア危機に対応するためのアイデアの1つが構造化プログラミング(GOTOレスプログラミング)である。これはGOTO分を廃して、プログラミングを基本三構造(順次進行、条件分岐、繰り返し)だけで表現することを目指すものである

GOTO文はハードウェアのメモリが少なく、CPU速度が遅い時代には、必要とされたが、プログラムの全体構造を複雑化させた

保守を容易にするために、プログラムをサブルーチンに分け、それらの独立性を高めることが重視される。独立性を高めるには、サブルーチンが共有するグローバル変数をなるべく少なくしないといけない

グローバル変数を避けるために、ローカル変数と引数の値渡しが考案された。

ローカル変数はサブルーチン内でのみ使用される変数であり、サブルーチンから抜ける際に破棄される。

引数の値渡しは、呼び出されたサブルーチンに引数として変数を使用する場合、変数の値をコピーして渡す仕組みである。そのため呼び出されたサブルーチン内で何をしても、その変数に影響を与えることがない。
(呼び出しサブルーチンによる変数の変更を、サブルーチン終了後にも反映させたい場合は参照渡しを行う)

構造化プログラミング言語に対応した構造化言語としてALGOL、Pascal、C言語などが登場した。

プログラミング言語の歴史は、機械語から始まり、理解しやすいようにアセンブリ言語や高級言語が作られ、保守しやすいように構造化言語へと発展した

図3.5参照

・【構造化言語の欠点】
構造化言語においても、サブルーチン終了後も情報を保持したい場合にはグローバル変数を使用せざるをえない

構造化言語において再利用可能なのはサブルーチンだけで、大規模なプログラムの再利用には十分ではない

「第4章 OOPは無駄を省いて整理整頓するプログラム」の読書記録

・【OOPとは何か】
OOPはオブジェクト指向プログラミング(Object Oriented Programming)の略称。クラス、ポリモーフィズム、継承の3つの機能を持つのが特徴

クラスは、関連性のあるサブルーチン(関数)とグローバル変数を整理して1つの部品のように扱うための仕組み。
グローバル変数の変更を行うサブルーチンを限定しやすくなるので、構造化言語が持つグローバル変数の問題を解決できる。

ポリモーフィズムと継承は、サブルーチンの共通化では対応できないプログラムの重複を回避する仕組み。
共通化できるロジックを増やすことで、構造化言語が持つ再利用の問題を解決できる。

・【クラス
クラスにより変数とサブルーチン(関数)はまとめられる。
まとめられたサブルーチンはメソッドと呼ばれ、まとめられたグローバル変数はインスタンス変数、属性、フィールドと呼ばれる。

構造化言語では関数名の重複を避けるために関数の名前が複雑化したが、クラスを使用すれば、クラス名で他のクラスのメソッドと区別できるので、メソッドの名前を単純化できる。

クラスのインスタンス変数へのアクセス権をprivateやprotectedなどに限定することで、インスタンス変数を隠すことができる。
アクセス可能メソッドを制限することで、エラー時の検証範囲を限定し、デバッグを容易にする。

クラスで定義した「インスタンス」を複数個作成することができる。
それらの各インスタンスは、クラスで定義されたインスタンス変数を異なるメモリ領域に持つことができる。
クラスを用いない場合は、複数個のグローバル変数を作成しないと、異なるメモリ領域を割り当てられない。

・【インスタンス変数】
インスタンス変数はインスタンスが破棄されるまでは情報が保持される。これはグローバル変数と類似した性質である

インスタンス変数はアクセス範囲を同じクラスのメソッドに限定できる。これはローカル変数と類似した性質である

インスタンス変数は、作成したインスタンスと同じ数だけ、メモリ内に変数領域を確保できる。これはグローバル変数にもローカル変数にもない性質である

・【ポリモーフィズム(多相性)】
1つのサブルーチンから異なるサブルーチンを呼び出せるようにする仕組み。(共通メインルーチンを作るということ)

・【継承】
複数のクラスにおいて共通したインスタンス変数やメソッドを別のクラスにまとめる仕組み。
この共通クラスをスーパークラスと呼び、これを利用するクラスをサブクラスと呼ぶ

サブクラスは呼び出し方法を共通にするため、メソッドの引数と戻り値の型をスーパークラスと同じにしないといけない

ポリモーフィズムの宣言と継承の宣言は同時に利用される。

・【クラス型】
変数の型指定、メソッドの引数の型、メソッドの戻り値の型にクラスを用いることができる。
それらに別のクラスのインスタンスは使用できない。

・【パッケージ】
複数のクラスをまとめる仕組み。
大規模プログラムの整理整頓に役立つ。名称の重複を避けるのにも役立つ

・【例外】
サブルーチンの戻り値とは異なる特別なエラーを返すことができる仕組み。


例外を宣言しているメソッドは、例外のロジックを正しく書かないとコンパイルエラーや実行エラーになる。
また、エラー処理をわざわざ書かなくても、エラーを上位のメソッドに伝えられる。

・【ガベージコレクション】
インスタンスを削除しないと、インスタンス作成時に確保したメモリはそのままになる。このメモリリークはメモリを圧迫してしまう。
一方、誤って使用中のインスタンスを削除すると、そのインスタンスを使用時にバグを起こす。

ガベージコレクタは、不要になったインスタンスをメモリ上から削除すること。
ガベージコレクションは、ガベージコレクタを自動的に実行する仕組み。
これにより不要なインスタンスだけを削除できる。

図4.16参照

・OOPを使用する時は、プログラムの保守と再利用を容易にするという目的意識をもって行うこと。

「第5章 メモリの仕組みの理解はプログラマのたしなみ」の読書記録

・【コンパイラ方式とインタプリンタ方式】
コンパイラ方式は、プログラムをコンパイラにより機械語に変換してから実行する。
インタプリンタ方式は、コンパイラを用いず、プログラムを逐次解析して実行する。

コンパイラ方式はインタプリンタ方式より実行速度が速い。機械語を直接に読んで実行するからである。

インタプリンタ方式は手軽に実行でき、機械語を用いないので環境に依存せず、異なる環境でも実行できる。

・【中間コード方式と仮想マシン】
中間コード方式は、プログラムをコンパイラにより機械語ではない中間コード(バイトコード)に変換し、それを仮想マシン(java VMなど)で機械語に変換して実行する。
仮想マシンは環境ごとに専用のものが用意されている。(Windows用java VMやLinux用java VMなど)

実行効率が良く、環境に依存しないで実行できる。

マイクロソフト社の.NETは複数のプログラミング言語に対する共通の仮想マシンになっており、これをCLR(共通言語ランタイム)と呼ぶ。

・【スレッド】
プログラムの実行単位。例えばワープロソフトならば、文書作成スレッド、スペルチェックスレッド、印刷スレッドなどの複数のスレッドが同時並行に動いている。

CPUは同時に1つのスレッドしか処理できないが、高速で実行スレッドを切り替えて管理することで、ユーザーには同時に複数スレッドを実行しているように見える。これをマルチスレッド環境と呼ぶ。

・【静的領域(static area)】
プログラム開始時から終了時までデータの配置が固定されるメモリ領域。
グローバル変数や定数、実行コードが配置される。

・【ヒープ領域】
動的に確保されたメモリ領域。必要な時に必要なサイズを確保し、不要になった時に解放する。
メモリ有効利用の為に、まとまった空き領域をなるべく大きく確保する。
複数のスレッドが共有し、スレッドの割り当て制御に用いられる。

・【スタック領域】
スレッド制御の為のメモリ領域。
各スレッドにスタック領域が1つずつ割り当てられる。

スレッドがサブルーチン(メソッド)を呼び出す際、その引数、ローカル変数、戻り値などをこのスタック領域に保存する。
スタック領域へのデータ格納はLIFO(後入れ先出し)方式で管理される。
スレッドはサブルーチンから更にサブルーチンを呼び出して実行するので、最新の実行中サブルーチンのデータをメモリから先に取り出せるLIFO方式が適している。

・【クラス固有情報のロード】
プラグラムは各クラス固有の情報(メソッドに書かれたコード情報)をメモリにロードする。
C言語やC++など多くのプログラミング言語は、プログラム開始時に定義されている全クラスの固有情報を一括してロードする。
Javaや.NETは、初めてクラスを使用する時に逐次ロードする。

逐次ロード方式は、毎回の読み込み時のオーバーヘッドで性能が落ちる代わりに、メモリ使用量を節約でき、分散したプログラムを結合するのが容易になるという利点がある。

クラス固有のコード情報は静的領域にロードされる。ただし、逐次ロード方式ではメモリの配置が変化するので、これをメソッドエリアと呼び分ける。

・【インスタンスの割り当て】
クラスからインスタンスを生成すると、その全インスタンス変数の合計サイズのヒープ領域が確保される。
また、メソッドエリアのクラス固有情報との紐づけも、この時に行われる。

OOPではないプログラミング言語は、サブルーチンの変数はスタック領域を用いて受け渡しするのに対して、OOPはヒープ領域を大量に使用するという特徴がある。

ヒープ領域の使用は、割り当て時のオーバーヘッドによる性能低下とメモリの解放忘れの危険がある。
しかしCPU性能の向上によりオーバーヘッドの影響は小さくなり、先述したガベージコレクションによりメモリリーク問題に対応できる。
それでも大量にインスタンスを生成する大規模アプリケーションでは、ヒープ領域がどれだけ必要になるか注意しないといけない。

・【インスタンス格納変数】
インスタンス生成時、ヒープ領域が確保される。インスタンスを格納する変数には、このヒープ領域のアドレスを指すポインタが格納される。
C++では直接にインスタンスを格納することも可能だが、Javaではポインタしか格納できない。
ポインタ方式は、インスタンスのサイズに依存せずに、インスタンスを格納する変数のサイズをアドレス長さのサイズに統一できる利点がある。

インスタンスを格納する変数を他の変数にコピーすると、ポインタがコピーされるだけなので、同じアドレスを指す変数が2つできてしまうことに注意する。
片方の値を変更すると、もう片方も同じ値になる。
インスタンスを格納する変数が、異なるヒープ領域を確保するには、new命令を用いてインスタンスを作成する必要がある。

・【ポリモーフィズムのメモリ管理】
ポリモーフィズムは1つのクラスから複数のクラスのメソッドを呼び出せる仕組みである。

ポリモーフィズムでは以下のようなメモリ管理を行う。
各クラスのメソッドは静的領域に確保される。そのメソッドのアドレスをメソッドテーブルに登録する。
ポリモーフィズムの呼び出し対象になるクラスのメソッドテーブルの形式は統一しないといけない。
呼び出す側のクラスは、呼び出し側が共通形式で持つメソッドテーブルを介して、インスタンスのメソッドを呼び出す。こうして1つのクラスから異なるクラスのメソッドを呼び出すことができる。

・【継承のメモリ管理】
スーパークラスのメソッドはサブクラスでも利用できる。
それらの継承したメソッドのコード情報は、サブクラスのメモリには展開せずに、スーパークラスのコード情報を利用する。
一方でサブクラスのメソッドテーブルには、サブクラスのメソッドだけではなく、スーパークラスから継承したメソッドも含めて定義する。
これによりサブクラスのインスタンスからもスーパークラスのメソッドを呼び出すことが可能となる。

スーパークラスから継承されたインスタンス変数は、サブクラスのインスタンスとしてコピーしてヒープ領域に確保する。

・【ガベージコレクションで削除対象になるインスタンス】
孤立したインスタンスはガベージコレクションの削除対象になる。
・誰からも参照されていないインスタンス
・循環参照で閉じているインスタンス群

「第6章 OOPがもたらしたソフトウェアとアイデアの再利用」の読書記録

・【OOPによるソフトウェアの再利用】
クラスライブラリのクラスのインスタンスを作成し、クラスライブラリのメソッドと変数定義を利用する

ポリモーフィズムを利用して、クラスライブラリのロジックをアプリケーション固有のものに置き換える

継承を利用して、クラスライブラリのクラスを拡張した新しいクラスを作成する


この記事が気に入ったらサポートをしてみませんか?