分類、共通化、抽象化が、ソースにおいてどのように表現されるかを見てみましょう。
プログラムを作成する場合、生産性を高めるためには、同じもの・共通するものをくくるテクニックが必要となります。そのために、変数、ループ、配列、サブルーチン、継承、インターフェイスなどを使います。
そうすることによって、以下のようなメリットがあります。
- 再利用可能
- 修正箇所が少ない
- ソースコードが短くなる
- 処理の明確化
先の分類に従うと、以下のようになります。
1. 抽象化(=交換可能)
変数、インターフェース
2. 共通化・正規化(=同じものは二度とかかない)
配列、繰り返し制御文、メソッド、継承
3 .分類(=類似したものを一つにまとめる)
構造体(クラス)、メソッド、クラス、パッケージ
円周、円の面積、球の体積を出すプログラムを書く場合、半径が5のとき、以下のようなソースを書きます。
System.out.println(5 * 2 * 3.14) ; System.out.println(5 * 5 * 3.14) ; System.out.println(5 * 5 * 5 * 3.14 * 4 / 3) ;
この場合半径を10に変えたいとき5の部分をすべて10に書き直さないといけません。また円周率を変えたい場合も全部書き直しが必要です。変数を使って、
int r = 5 ;
float pai = 3.14f ;
System.out.println(r * 2 * pai) ;
System.out.println(r * r * pai) ;
System.out.println(r * r * r * pai * 4 / 3) ;
とすれば、一ヶ所書き換えるだけ済みます。プログラムの中では具体的な値を用いないことが、再利用性を高めることになるのです。
例えば、
1.______________________________ 2.______________________________ 3.______________________________
というフォームを表示したい場合、ループを使わない場合、
System.out.println("1.______________________________") ; System.out.println("2.______________________________") ; System.out.println("3.______________________________") ;
となるのですが、行が多くなると、この方法ではソースが長くなってしまいます。ループを使うと、
for (int i = 1 ; i < 50 ; i++) { System.out.println(i + ".______________________________") ; }
と短く書くことが可能になります。
半径5, 10, 15, 20, 25の円の円周を表示したい場合、
System.out.println(5 * 2 * 3.14) ; System.out.println(10 * 2 * 3.14) ; System.out.println(15 * 2 * 3.14) ; System.out.println(20 * 2 * 3.14) ; System.out.println(25 * 2 * 3.14) ;
となりますが、メソッドを利用すると、
enshu(5) ; enshu(10) ; enshu(15) ; enshu(20) ; enshu(25) ; .... void enshu(int r) { System.out.println(r * 2 * 3.14) ; }
とシンプルに書くことができます。
人の名前を順次表示したい場合、配列を使用しないと、
String name1 = "Yamada" ; String name2 = "Tanaka" ; String name3 = "Sato" ; String name4 = "Suzuki" ; System.out.println(name1) ; System.out.println(name2) ; System.out.println(name3) ; System.out.println(name4) ;
といくつも変数を用意しなければいけないのですが、配列を使うと、
String[] name = {"Yamada","Tanaka","Sato","Suzuki"} ; for (int i = 0 ; i < name.length ; i++) { System.out.println(name[i]) ; }
と一つの変数で済みます。
String[] name = {"Yamada","Tanaka","Sato","Suzuki"} ; String[] adress = {"Tokyo","Saitama","Kanagawa","Chiba"} ; int[] age = {20, 25, 27, 30} ; for (int i = 0 ; i < name.length ; i++) { hyouji(name[i], adress[i], age[i]) ; } ... void hyouji(String name, String address, int age) { System.out.println(name + " " + address + " " + age + "歳") ; }
となるのですが、この場合、名前、住所、年齢の配列の中の順序を一致させておく必要があり、またメソッドにこのセットを渡したい場合、上記のように三つとも渡す必要があります。ここでクラスを使うと、
class Person { String name ; String address ; int age ; Person(String name, String address, int age) { this.name = name ; this.address = address ; this.age = age ; } } Person[] p = new Person[4] ; p[0] = new Person("Yamada", "Tokyo", 20) ; p[1] = new Person("Tanaka", "Saitama", 25) ; p[2] = new Person("Sato", "Kanagawa", 27) ; p[3] = new Person("Suzuki", "Chiba", 30) ; for (int i = 0 ; i < p.length ; i++) { hyouji(p[i]) ; } void hyouji(Person p) { System.out.println(p.name + " " + p.address + " " + p.age + "歳") ; }
というようにセットで管理することができ、Personデータをメソッド間で受け渡ししたい場合、扱いが簡単になります。
また、クラスとインスタンスの関係は、変数と具体的な数との関係でもあります。クラスは抽象的なもので、型を作り、インスタンスはそこに具体的な値をセットします。
継承を使うと、親クラスのメソッドを子クラスがそのまま利用できます。
class A { void shoriX() { ..... } } class B extends A { void shoriY() { } // Bの中にshoriX()を書かなくてもよい } class C extends A { void shoriY() { } // Cの中にshoriX()を書かなくてもよい }
B, Cでは、Aのメソッドは共通化されています。また、Aと親子関係があることで、グループ化されます。
インターフェイスあるいは親クラスと子クラスの関係は、変数と値の関係にたとえることができます。例えばクラスAにあるメソッドを使う場合、
A a = new A() ; a.exec1() ; a.exec2() ; a.exec3() ;
のように書きます。これをインターフェイスを使って書くと、
Interf inf = new A() ; inf.exec1() ; inf.exec2() ; inf.exec3() ;
となり、もしクラスBのメソッドに書き換えたい場合、修正はnew A()の部分をnew B()に変えるだけで済みます。但し、A,BはInterfに定義されているメソッドをすべて実装している必要があります。
変数のメリットはどんな値でも入れて処理を共通化できることで、インターフェイスや抽象クラスのメリットは、どんな子クラスのインスタンスを入れて処理を共通化できることにあります。変数には具体的な値が入り、インターフェースを実装したクラスには具体的な処理が入ります。そしてそれぞれ入れ替えが可能です。
つまり、インターフェースを導入することで、処理をも抽象化することができます。これがオブジェクト指向において重要なポリモーフィズムの基礎となっています。