抽象クラス
「トッピングがないラーメンなどもはやラーメンとして未完成である.ラーメンとして存在することは許されない」そう語る職人がいた.トッピングにて何かラーメンにトッピングされない限り,ラーメンとして提供できない仕組みはできないだろうか.すなわち,トッピングが決まったらラーメンとしてつくることができるように,トッピングをのせることは決まっていて,○○ラーメンをメニュー化するときには,載せるトッピングを必ず決めるようにすればよい.
ラーメンの例では,ラーメンの定義として,「トッピングを載せる」という振る舞いは決まっているが,その中身は決まっていない.○○ラーメンとしてメニュー化するときには,何をトッピングに載せるか決めなければならない.そのような振る舞い,すなわちメソッドのことを「抽象メソッド」とよぶ.「トッピングを載せる」という振る舞いの名称のみが決まっていて,振る舞いの具体的な内容が決まっていないクラスのことを,「抽象クラス」という.
「抽象メソッド」をもつ「抽象クラス」は,インスタンス化できない.なぜならば,具体的な処理内容が未定である「抽象メソッド」をもつので,インスタンス化してメソッドが呼ばれたときに,どのように振舞えばよいか定まらないからである.
したがって,インスタンス化するには「抽象クラス」の「抽象メソッド」の振る舞いを決める必要があるので,「抽象クラス」はサブクラスを持つ.サブクラスで振る舞いを決定する.すなわち,「抽象メソッド」をオーバーライドする.オーバーライドしたクラスでは,具体的な振る舞いが決定しているので,インスタンス化できる.
Ramenクラスでは,setToppingメソッドが抽象メソッドとして定義されている.そのため,抽象メソッドを含むクラスも「抽象クラス」として定義されなければならない.
public abstract class Ramen { protected int katasa;//1:固い,2:普通,3:柔い protected int ryou;//1-10 protected Taste soup; protected String Topping; Ramen() { System.out.println("ラーメンつくるよ"); } public void setRyou(int ryou) { this.ryou = ryou; System.out.println("量:" + ryou); } public void setKatasa(int katasa) { this.katasa = katasa; System.out.println("固さ:" + katasa); } public void setSoup(Taste taste) { this.soup = taste; System.out.println("スープ:" + this.soup); } public void checkRamen() { System.out.print("量:" + ryou); System.out.print("固さ:" + katasa); System.out.print("スープ:" + soup); System.out.println("トッピング:" + topping); } abstract public void setTopping(); }
このRamenクラスを継承して,MisoRamenクラスを作る.この時に重要なのは,スーパークラスで定義された,抽象メソッドを実装する必要があることである.実装するとは,具体的な処理内容を定義することである.
public class MisoRamen extends Ramen { MisoRamen() { System.out.println("みそラーメンを作るよ"); } public void setTopping() { topping = "ニンニク"; System.out.println("トッピング:" + topping); } public void setSoup(Taste taste) { this.soup = Taste.みそ; System.out.println("スープ:" + this.soup); } }
この関係をUMLのクラス図で表すと,以下のようになる.ここで注意してほしいのは,Ramenクラスのクラス名「Ramen」,Ramenクラスに含まれるメソッド「setTopping」が斜体になっていることである.
斜体で表現されているものは,「抽象」であることを意味している.クラス図から「Ramenクラス」が抽象クラスであることが分かり,「setTopping」が抽象メソッドであることが分かる.さらに,Ramenクラスを継承するクラスで,実装していることがわかる.
このとき,抽象的なメソッド名を含むクラスは,インスタンス化することができないことを,以下のように確認することができる.そのため,抽象メソッドを持つクラスは「抽象クラス」として定義されている.
public class Main { public static void main(String[] args) { //みそラーメン System.out.println("===みそラーメン==="); MisoRamen misoRamen = new MisoRamen(); misoRamen.setRyou(5); misoRamen.setKatasa(2); misoRamen.setSoup(Taste.しょうゆ); misoRamen.setTopping(); misoRamen.checkRamen(); } }
===みそラーメン=== ラーメンつくるよ みそラーメンを作るよ 量:5 固さ:2 スープ:みそ トッピング:ニンニク 量:5固さ:2スープ:みそトッピング:ニンニク
サブクラスでメソッドを具体的にメソッドを定義するのだから,スーパークラスであえて名称だけでも定義する必要性はなんだろうか? スーパークラスでメソッドを抽象クラスとして定義することで,そのクラスを継承するクラスでは,必ず,抽象メソッドを定義しなければならない.これによって,同じ抽象クラスを継承しているクラスが,抽象クラスで宣言されているメソッドを持つことを約束できる.
もちろん,サブクラスで抽象メソッドを実装しないという判断もできる.しかし,その時には,継承したサブクラスは実装できない抽象クラスになる.そのため,そのサブクラスを継承したクラスで実装する必要がある.いずれにしても,いずれかの段階で実装しなければ,インスタンス化できない.