抽象クラス

「トッピングがないラーメンなどもはやラーメンとして未完成である.ラーメンとして存在することは許されない」そう語る職人がいた.トッピングにて何かラーメンにトッピングされない限り,ラーメンとして提供できない仕組みはできないだろうか.すなわち,トッピングが決まったらラーメンとしてつくることができるように,トッピングをのせることは決まっていて,○○ラーメンをメニュー化するときには,載せるトッピングを必ず決めるようにすればよい.

ラーメンの例では,ラーメンの定義として,「トッピングを載せる」という振る舞いは決まっているが,その中身は決まっていない.○○ラーメンとしてメニュー化するときには,何をトッピングに載せるか決めなければならない.そのような振る舞い,すなわちメソッドのことを「抽象メソッド」とよぶ.「トッピングを載せる」という振る舞いの名称のみが決まっていて,振る舞いの具体的な内容が決まっていないクラスのことを,「抽象クラス」という.

「抽象メソッド」をもつ「抽象クラス」は,インスタンス化できない.なぜならば,具体的な処理内容が未定である「抽象メソッド」をもつので,インスタンス化してメソッドが呼ばれたときに,どのように振舞えばよいか定まらないからである.

したがって,インスタンス化するには「抽象クラス」の「抽象メソッド」の振る舞いを決める必要があるので,「抽象クラス」はサブクラスを持つ.サブクラスで振る舞いを決定する.すなわち,「抽象メソッド」をオーバーライドする.オーバーライドしたクラスでは,具体的な振る舞いが決定しているので,インスタンス化できる.

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スープ:みそトッピング:ニンニク

サブクラスでメソッドを具体的にメソッドを定義するのだから,スーパークラスであえて名称だけでも定義する必要性はなんだろうか? スーパークラスでメソッドを抽象クラスとして定義することで,そのクラスを継承するクラスでは,必ず,抽象メソッドを定義しなければならない.これによって,同じ抽象クラスを継承しているクラスが,抽象クラスで宣言されているメソッドを持つことを約束できる.

もちろん,サブクラスで抽象メソッドを実装しないという判断もできる.しかし,その時には,継承したサブクラスは実装できない抽象クラスになる.そのため,そのサブクラスを継承したクラスで実装する必要がある.いずれにしても,いずれかの段階で実装しなければ,インスタンス化できない.

演習
トッピングとして「コーンバター」を乗せた,コーンバターみそラーメンを作成せよ.味はみそ味のみとする.