はじめに
プログラムを書いててなんとなく使っていたオブジェクト指向
今回は、そんな曖昧に捉えがちなオブジェクト指向の概念やできることについて、三大要素のサンプルコードを交えつつまとめてみました
サンプルコードはオブジェクト指向の概念をしっかりと踏襲したJavaを用いています
オブジェクト指向の定義
オブジェクト指向とは、「ソフトウェア開発をする時に用いる部品化の考え方」のこと
この部品化の考え方にそって、一つのプログラムを複数に部品化すると、その内容が把握しやすくなり、我々人間の頭が追いつかない状況を避けることが出来る
なぜオブジェクト指向で書くのか?
なぜオブジェクト指向で書くのかを考えたことはありますか?
オブジェクト指向で書く理由、それは変更に対して柔軟に対応するため
例えば、ある都市に複数の新しいコーヒーショップチェーンを展開するシステムを設計するとします。次第に店舗数が増加すると予想されるので、この「店舗追加」という動作は頻繁に行われる可能性が高い。このような変更が頻繁に起こる部分は、クラスとして独立させて設計すると良い。そうすれば、未来に「新しい店舗を追加する」という変更が必要になったとき、システムはその変更に簡単に適応できるようになる。
オブジェクト指向によるアプリケーション開発は、変更されない箇所を軸に、頻繁に変更されるであろう箇所をクラスに抽出するプログラミングスタイルである
オブジェクト指向のメリット
また、オブジェクト指向を用いる根底にある目的は、「人間が内容を把握しやすいプログラム開発を実現する」というもの
オブジェクト指向の考え方を用いて賢い部品化をおこなうと以下のようなメリットを得ることができる
- プログラムの変更が容易になる(柔軟性・保守性の向上)
- プログラムの一部を簡単に転用できる(再利用性の向上)
これらのメリットは実際の開発業務を経験していく中で、幾度となくコードの修正をおこなう事によって初めて体感できるメリットである
つまり、要約すると
ラクして、楽しく、良いものを作れる!
これにより、創造性を膨らませて色々な機能を実装することに開発者は集中できて、それ以外の仕組みの部分ではオブジェクト指向の考え方のレールに乗っかり、効率よくサービスを作っていけるような気遣いであると捉えることができる
オブジェクト指向の3大要素について
1.継承
継承とは、親クラス(スーパークラス)の特徴を受け継いだ子クラス(サブクラス)を作る仕組みのこと
オブジェクト指向の花形機能といえる
子クラスには以下の特徴がある
- 子クラスは親クラスのプロパティやメソッドを引き継ぐ
- 子クラス独自のプロパティやメソッドを作ることもできる
継承のメリット
- コードの再利用性 : 一度定義したクラスの機能や特性を再利用することができる
- 保守性の向上 : 共通の機能や特性を親クラスにまとめることで、修正や変更が必要な場合にもその影響を最小限に抑えることができる
- 拡張性の向上 : 基底クラスに新しい機能や特性を追加するだけで、それを継承したすべての子クラスにその機能を適用することができる
継承の本質
継承の本質はインターフェースである
Javaでは、あるクラスは一つのスーパークラスのみを継承することができる(単一継承)
しかし、クラスは複数のインターフェースを同時に実装(implements)することができる
(実装するとは、親インターフェースで未定だった各メソッドの内容をオーバーライドし実装して確定させるという意味)
したがって継承は、親クラスから機能を受け継ぐためのものではなく、継承の本質は、交換可能なパーツを作成するために共通点を「規格」としてまとめ上げられるインターフェースであるといえる
サンプルコード(継承)
/*
* 【インターフェースの定義①】
* 抽象クラスのうち、基本的に抽象メソッドしか持たないものを「インターフェース」として特別扱いできる
* 複数のインターフェースを親とする多重継承が許されている
* インターフェースを親に持つ子クラスの定義にはimplementsを用いる
*/
public interface Creature {
public abstract void run(); // <= void run(); のみでも可
}
/*
* 【インターフェースの定義②】
* オーバーライドして処理の内容を確定していないので、implements(実装)ではなく、extends(拡張)を使っている
*/
public interface Human extends Creature {
void talk();
void watch();
void hear();
// さらに親インターフェースからrun()を継承
}
/*
* 【抽象クラス】
* newによるインスタンス化が禁止されている
* 中身を決定できない「詳細未定のメソッド」には、abstractをつけて抽象メソッドとする
* 抽象メソッドを1つでも含むクラスは、abstractをつけた抽象クラスにしないといけない
* インターフェースとは違い、フィールドを持つ
*/
public abstract class Character {
String name;
int hp;
public void run() {
System.out.println(this.name + "は逃げ出した");
}
public abstract void attack(Enemy e);
}
/*
* 【通常のクラス定義】
* Characterからhpやnameなどのフィールドを継承
*/
public class Hero extends Character implements Human {
// Characterから継承した抽象メソッドattack()を実装
public void attack(Enemy e) {
System.out.println(this.name + "の攻撃!");
}
// さらにHumanから継承した4つの抽象メソッドを実装
public void talk() {...};
public void watch() {...};
public void hear() {...};
public void run() {...};
}
2.多態性(ポリモーフィズム)
多態性とは、オブジェクト指向プログラミングの中心的な概念の一つ
あるものを、あえてザックリ捉えることで、さまざまなメリットを享受しようという機能
具体的には以下の二つの形態をとる
-
コンパイル時の多態性(静的多態性)
- オーバーロード(関数の多重定義)を指す。同じクラス内でメソッド名が同じでも、引数の型や数が異なると、異なるメソッドとして扱われる特性
-
実行時の多態性(動的多態性)
- オーバーライド(関数の再定義)を指す。サブクラスがスーパークラスのメソッドをオーバーライドすると、そのサブクラスのオブジェクトはスーパークラスの型で参照されたとしても、サブクラスのメソッドが実行される特性
サンプルコード(多態性)
public abstract class Monster {
public void run() {
System.out.println("モンスターは逃げ出した");
}
}
public class Slime extends Monster {
// runメソッドをオーバーライド
public void run() {
System.out.println("スライムはササっと逃げ出した");
}
// fireballメソッドを追加
public void fireball() {
System.out.println("スライムは火の玉を吐き出した")
}
}
public class Main {
public static void main(String[] args) {
Slime slime = new Slime();
Monster monster = new Slime();
slime.run(); //=> スライムはササっと逃げ出した
monster.run(); //=> スライムはササっと逃げ出した
slime.fireball(); //=> スライムは火の玉を吐き出した
monster.fireball(); //=> エラー
}
}
多態性の本質
継承の本質はインターフェースであると説明しましたが、多態性(ポリモーフィズム)の本質はそのインターフェース(抽象・規格)に対してプログラムをするという事
Monster monster = new Slime();
monster.run(); //=> slime.run();でないため、抽象に対してプログラミングしている
重要ポイント
- 箱の型 : どのメソッドを「呼べるか」を決定する
- 中身の型 : メソッドが呼ばれたら、「どう動くか」を決定する
多態性を意識したコードを書くには「抽象」が大切ですが、抽象に対してプログラムをするということは、具体に対してプログラムしないようにするということでもある
3.カプセル化
カプセル化は、データ(変数)とそのデータを操作するメソッド(関数)を一緒にクラス内にまとめ、外部から直接アクセスできないよう制御すること
こうすることで、内部の実装の詳細を隠蔽し、外部からの不正なアクセスやありえない値が入るなどの変更を防ぐことができる
サンプルコード(カプセル化)
public class Hero {
private String name;
// getterメソッド
public String getName() {
return this.name;
}
// setterメソッド
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Hero h = new Hero();
h.setName("Java次郎");
System.out.println(h.getName()); //=> Java次郎が出力される
}
}
カプセル化の本質
カプセル化は、継承や多態性に比べ重要かつ難しい概念であるといえる
継承や多態性と比べ、カプセル化は設計の際に適切な境界を設けることが求められるため、難易度が高い...
例をあげると、自動車のカプセルを開けたら(すなわち車を分解したら)、バラバラになった自動車を元には戻せない
しかし、そんな複雑な自動車を人は運転することができる
なぜなら、ハンドルを操作しアクセルを踏めば前に進むことがわかっているから
つまり、運転する人が内部の余計な設計を知らず・考えずとも運転できるようになっている
上記より、カプセル化とは、抽象化のことであり、無駄を省き洗練されて分かりやすいものを作るという事といえる
まとめ
- オブジェクト指向とは、ソフトウェアを開発する際に用いる部品化の考え方
- オブジェクト指向を用いると、大規模で複雑なソフトウェアであっても、ラクして、楽しく、良いものを開発することができる
- オブジェクト指向に不可欠な概念は多態性であり、多態性は抽象に対してプログラミングすることで生まれる
- 継承の本質は、機能の引き継ぎではなく、共通点を抽象的にまとめ上げられるインターフェースである
- 多態性の本質は、インターフェイス(抽象・規格)に対してプログラムするということ
- カプセル化とは無駄を省き洗練させてわかりやすいものを作るということ
- カプセル化は、継承や多態性とは比較にならないほど重要