Javaは「オブジェクト指向言語」と言われていますが、それは単に「Javaプログラム=オブジェクト指向」といっている訳ではありません。「オブジェクト指向」に基づいての設計があってこそ、その恩恵を受けることが出来るのです。
ここでは「オブジェクト指向の三大要素」と呼ばれる次の項目について、その実装方法をみていきたいと思います。
但し、上記の要素が含まれていればオブジェクト指向であるとうわけではないことに注意してください。
オブジェクト指向の本質はインスタンスをより独立した「オブジェクト」(もの)として扱えるように向かわせる(指向)ことです。つまり各オブジェクトの独立性を高めていくことがオブジェクト指向の手法であり、具体的にはインスタンスへのアクセス方法の整備とインスタンス間の結びつきを弱める(汎用的にする)ことだと言えます。
これを踏まえて、オブジェクト指向三大要素の各手法をみていきます。
カプセル化とはクラス(インスタンス)を一つのオブジェクトとしての独立性を高めようとするものです。
具体的に言うと、「メンバ変数」や「メソッド」に対して「アクセス修飾子」を適切に設定し、クラス(インスタンス)に対するアクセス制御を行います。これによりクラス(インスタンス)のアクセス(使用方法)が統一されて独立性が高まり、その結果、予期せぬバグを防いだり、クラス(インスタンス)の変更や組換えなどが容易になります。
「ねこクラス(Cat1101)」を例にカプセル化を考えます。このクラスはメンバーにcry変数(鳴き声)を持っていますが、アクセス修飾子がprivateになっていますので、外部から直接変更されることはなく、おもわぬ鳴き声に変更されてしまうようなことはありません。またcry変数へのアクセスは次のメソッド「bark()(鳴き声を出力)」、「changeCry()(鳴き声を変える)」だけとなっているので「ねこクラス」のバグや仕様変更(鳴き声を変える、バリエーションを増やすなど)があった場合、これらのメソッド内で変更を吸収することができ、他のクラスへの影響を押さえることが出来ます。
・サンプルソース(Sample1101.java)
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 = "ニャ~オ"; } }
・実行結果
C:\dev\java>javac Sample1101.java [Enter] C:\dev\java>java Sample1101 [Enter] ねこ : ニャンニャンニャン ねこ : ニャ~オ
メンバーへのアクセス修飾子を適切に設定することで、予期せぬバグを防いだり、仕様変更などを行う際に他のクラスへの影響を少なくすることができる。つまりクラスの独立性が高まったことになります。
継承は別のクラスの機能をすべて引き継ぐことができるというとても便利なものです。しかし、これをもって継承はオブジェクト指向の三大要素の一つだといっているのではありません。むしろオブジェクト指向の観点からすると、単に別のクラスにある機能を取り入れたいだけで継承を用いるべきではないといえます。ここではオブジェクト指向の観点から継承の取り扱いをみていきます。
継承関係とは「is a」関係つまり「親クラスA」と「子クラスB」があった場合に「B is A」(BはAである)となります。よってBをAとして取り扱うことも可能になるわけです。これはクラス間の連携をより抽象的(汎化)することでオブジェクトの独立性を高めるという、 オブジェクト指向設計における最も重要な要素の1つを実現しています。(インタフェースを実装することも同様の効果があります。)
例えば、親クラスに「動物クラス(Animal1102)」、そしてそれを継承した「ねこクラス(Cat1102)」があったとします。この場合、ねこクラスを動物クラスとして取り扱うことが可能となります。
・サンプルソース
public class Sample1102 { public static void main (String[] args) { Cat1102 pet = new Cat1102(); // 「ねこクラス」は次のように「動物クラス」型変数に代入して使用することも可能です。 // Animal1102 pet = new Cat1102(); // 引数が「動物クラス」の場合、「ねこクラス」のインスタンスを「動物クラス」のインスタンスとして使用できます。 PetCare1102.giveBait(pet); } } // 動物クラス 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("エサ"); } }
・実行結果
C:\dev\java>javac Sample1102.java [Enter] C:\dev\java>java Sample1102 [Enter] エサを食べました。
クラス連携の際、実クラスではなくその親クラスを使用することで、クラス間の結びつきが弱まり、より汎用的になります。従ってクラス(オブジェクト)の独立性を高める事が出来ます。
「ポリモーフィズム」とは、「抽象クラス」や「インターフェース」などを利用してメソッドの呼び出し方法を共通化し、さらに「オーバーライド」させることで同じメソッドを呼び出しても、実際のインスタンス毎にその挙動を変化させようとするものです。
説明よりも実際の動きをみたほうが理解しやすいと思います。
「動物クラス(Animal1103)」を継承した「ねこクラス(Cat1103)」、「いぬクラス(Dog1103)」、「かえるクラス(Frog1103)」、「あひるクラス(Duck1103)」があります。「呼び出しクラス(Sample1103)」は「ペットクラス」から取り出した「動物クラス」のメソッド(.bark())を呼ぶだけですが、その挙動は実際に呼び出されるインスタンス(「ねこ」、「いぬ」、「かえる」、「あひる」)により変化します。
・サンプルソース(Sample1103.java)
public class Sample1103 { public static void main (String[] args) { Animal1103[] animal = Pet1103.getAnimal(); for(Animal1103 pet: animal) { // 実際のインスタンスの区別なく動物が鳴くという処理(bark())のみで済ますことができます。 pet.bark(); } } } // ペットクラス class Pet1103 { public static Animal1103[] getAnimal() { Animal1103[] animals = {new Cat1103(), new Dog1103(), new Frog1103(), new Duck1103()}; return animals; } } // 動物クラス(抽象クラス) 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("あひる:がぁーがぁーがぁー"); } }
・実行結果
C:\dev\java>javac Sample1103.java [Enter] C:\dev\java>java Sample1103 [Enter] ねこ:ニャンニャンニャン いぬ:ワンワンワン かえる:ガァーガァーガァー あひる:がぁーがぁーがぁー
親クラス(共通クラス)の型でクラス連携及びメソッドの呼び出しまで実インスタンスを意識することなく行えるため、クラスの独立性を高めることになります。