11 章: オブジェクト指向

オブジェクト指向

オブジェクト指向の三大要素:

1. カプセル化
2. 継承
3. ポリモーフィズム

ただし, 上記の要素が含まれていればオブジェクト指向であるというわけではないことに注意すること.

オブジェクト指向の本質はインスタンスをより独立した [オブジェクト] (もの) として扱えるように向かわせる (指向) こと. 独立性を高めるとは, インスタンスへのアクセス方法の整備とインスタンス間の結びつきを弱める (汎用的にする) ことだと言える.

カプセル化

カプセル化とはクラス (インスタンス) を一つのオブジェクトとしての独立性を高めようとするもの.

具体的に言うと, [メンバ変数] や [メソッド] に対して [アクセス修飾子] を適切に設定し, クラス (インスタンス) に体するアクセス制御を行う. これによりクラス (インスタンス) のアクセス (使用方法) が統一されて独立性が高まり, その結果, 予期せぬバグを防いだり, クラス (インスタンス) の変更や組み換えが容易になる.

カプセルの例

[猫クラス (Cat1101)] を例にカプセル化を考える. このクラスはメンバに cry 変数 (鳴き声) を持っているが, アクセス修飾子が private になっているので, 外部から直接に変更させることはなく, 思わぬ鳴き声に変更されてしまうようなことはない. また cry 変数へのアクセスは次のメソッド [bark() (鳴き声を出力)], [changeCry() (鳴き声を変える)] だけとなっているので [猫クラス] のバグや仕様変更 (鳴き声を変える, バリエーションを増やすなど) があった場合, これらのメソッド内で変更を吸収することができ, 他のクラスへの影響を押さえることができる.

Sample1101.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Sample1101{
    public static void main(String[] args){
	Cat1101 cat = new Cat1101();

	// 猫クラス (Cat1101) へのアクセス
	cat.bark(); // 鳴き声を聞く
	cat.changeCry(); // 鳴き声を変える
	cat.bark(); // 鳴き声を聞く
    }
}

// 猫クラス
class Cat1101{
    // private 修飾子により, 外部から直接この cry 変数 (鳴き声) を変更することはない
    private String cry = "ニャンニャンニャン";

    // cry 変数 (鳴き声) を出力するのはこのメソッド
    public void bark(){
	System.out.println("猫: " + cry);
    }

    // cry 変数 (鳴き声) を変更できるのはこのメソッド
    public void changeCry(){
	this.cry = "ニャーオ";
    }
    
}

上記のプログラムの実行結果:

[wtopia 11]$ java Sample1101
猫: ニャンニャンニャン
猫: ニャーオ

カプセル化のポイント

メンバへのアクセス修飾子を適切に設定することで, 予期せぬバグを防いだり, 仕様変更などを行う際に他のクラスへの影響を少なくすることができる.

つまり:

クラスの独立性が高まったことになる.

継承

継承は別のクラスの機能をすべて引き継ぐことができるというとても便利なもの. しかし, これをもって継承はオブジェクト指向の三大要素の一つだといっているのではない. むしろオブジェクト指向の観点からすると, 単に別のクラスにある機能を取り入れたいだけで継承を用いるべきではないといえる. ここではオブジェクト指向の観点から継承の取り扱いをみていく.

継承関係とは [is a] 関係, つまり [親クラス A] と [子クラス B] があった場合に [B is A] (B は A である) となる. よって B を A として取り扱うことも可能になるわけ. これはクラス間の連携をより抽象的 (汎化) することでオブジェクトの独立性を高めるという, オブジェクト指向設計における最も重要な要素の一つを実現している. (インタフェースを実装することも同様の効果がある)

継承の例

例えば, 親クラス [動物クラス (Animal1102)], そしてそれを継承した [猫クラス (Cat1102)] があったとする. この場合, 猫クラスを動物クラスとして取り扱うことが可能となる.

Sample1102.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Sample1102{
    public static void main(String[] args){
	Cat1102 pet = new Cat1102(); // pet.bark() が使える
	// [猫クラス] は次のように [動物クラス] 型変数に代入して使用することも可能
	// Animal1102 pet = new Cat1102(); を使う時に, pet.bark() が使えなくなる.
	// 引数が [動物クラス] の場合, [猫クラス] のインスタンスを [動物クラス] のインスタンスとして使用できる.
	PetCare1102.giveBait(pet); // giveBait() メッソドの前に static がないとこの書式は使えない.
    }
}

// 動物クラス
class Animal1102{
    public void eat(String food){
	System.out.println(food + "を食べた");
    }
}

// 猫クラス
class Cat1102 extends Animal1102{
    private final String CRY = "ニャンニャンニャン";

    public void bark(){
	System.out.println("猫: " + CRY);
    }
}

// ペットの世話用クラス
class PetCare1102{
    // 引数が [動物クラス] なので, [ペットの世話用クラス] は [猫クラス] 専用のクラスではなくなる. (汎用化)
    public static void giveBait(Animal1102 animal){
	animal.eat("エサ");
    }
}

上記のプログラムの実行結果:

[wtopia 11]$ java Sample1102
エサを食べた

継承のポイント

クラス連携の際, 実クラスではなくその親クラスを使用することで, クラス間の結びつきが弱まり, より汎用的になる. 従って:

クラス (オブジェクト) の独立性を高めることができる.

ポリモーフィズム (多態性)

[ポリモーフィズム] とは, [抽象クラス] や [インタフェース] などを利用してメソッドの呼び出し方法を共通化し, さらに [オーバーライド] させることで同じメソッドを呼び出しても, 実際のインスタンス毎にその挙動を変化させようとするもの.

[動物クラス (Animal1103)] を継承した [猫クラス (Cat1103)], [犬クラス (Dog1103)], [蛙クラス (Frog1103)], [アヒルクラス (Duck1103)] がある. [呼び出しクラス (Sample1103)] は [ペットクラス] から取り出した [動物クラス] のメソッド [.bark()] を呼ぶだけだが, その挙動に実際に呼び出されるインスタンス ([猫], [犬], [蛙], [アヒル]) により変化する.

Sample1103.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Sample1103{
    public static void main(String[] args){
	Animal1103[] pet = Pet1103.getAnimal();
	for(int i = 0; i < pet.length; i++){
	    pet[i].bark();
	}
    }
}

// ペットクラス
class Pet1103{
    public static Animal1103[] getAnimal(){
	Animal1103[] animal = new Animal1103[4]; // int[] array = new int[10];
	animal[0] = new Cat1103();
	animal[1] = new Dog1103();
	animal[2] = new Frog1103();
	animal[3] = new Duck1103();

	return animal;
    }
}

// 動物クラス (抽象クラス)
abstract class Animal1103{
    public abstract void bark(); // 抽象メソッド, 具体的な中身がない
}

// 猫クラス
class Cat1103 extends Animal1103{
    public void bark(){
	System.out.println("猫: ニャンニャンニャン");
    }
}

// 犬クラス
class Dog1103 extends Animal1103{
    public void bark(){
	System.out.println("犬: ワンワンワン");
    }
}

// 蛙クラス
class Frog1103 extends Animal1103{
    public void bark(){
	System.out.println("蛙: グアーグアーグアー");
    }
}

// アヒルクラス
class Duck1103 extends Animal1103{
    public void bark(){
	System.out.println("アヒル: ガーガーガー");
    }
}

上記のプログラムの実行結果:

[wtopia 11]$ java Sample1103
猫: ニャンニャンニャン
犬: ワンワンワン
蛙: グアーグアーグアー
アヒル: ガーガーガー

ポリモーフィズムのポイント

親クラス (共通クラス) の型でクラス連携およびメソッドの呼び出しまで実インスタンスを意識することなく行えるため:

クラスの独立性を高めることになる.

注意:

抽象クラスはインスタンスが作れないけど, ポリモーフィズムを実装するときに, よく使われている.