読者です 読者をやめる 読者になる 読者になる

Chapter 2. 継承というものがあるがこれは一体全体、何がしたいのか

設計 Java

Java という言語の記事です。
 継承を使うと、例えばこういうことができます。
 
 ・値段を「確実に」表示する関数を作る。
 
 もしそんな関数が作れるのであれば、Chapter 1. で例示したような、Melon でも Apple でもどっちを受け取っても良いような、そんな機能を作れますよね。

class Main {
    
    // 値段を「確実に」表示する関数
    public void printPrice( Apple または Melon f ) {
        System.out.println( f.price );
    }
    
   public void main( String[] args ) {
        Apple a = new Apple( 100 );
        printPrice( a );
        
        Melon m = new Melon( 500, 100 );
        printPrice( m );
    }
    
}

まぁ、↑このコードは「Apple または Melon なんて都合の良い解釈してられるかボケ!」と怒られてコンパイルエラーになるのですが、それを表現する方法が Java には用意されています。それが継承です。

 「Apple または Melon」とは何でしょうか。Apple と Melon はそれぞれモデリングした定義でした。ここからさらに Apple と Melon の共通項をモデリングします。

class Fruit {
    
    public Fruit( int price ) {
        this.price = price;
    }
    
    public int price;
    
}

 Fruit クラスの定義です。Fruit を利用して、「Apple も Melon も Fruit の一種だ」という意味に定義し直せば良いのです。では Apple と Melon を書き直します。文法の紹介なのでさらっといきましょうか。

class Apple extends Fruit {
    
    public Apple( int price_a ) {
        super( price_a );
    }
    
}

class Melon extends Fruit {
    
    public Melon( int price_m, int weight ) {
        super( price_m );
        this.weight = weight;
    }
    
    public int weight;
    
}

「A extends B」は「A は B である」という意味です。Apple は Fruit であり、Melon も Fruit である、という意味になります。これを使うとこう書けます。

class Main {
    
    // 値段を「確実に」表示する関数
    public void printPrice( Fruit f ) {
        System.out.println( f.price );
    }
    
    public void main( String[] args ) {
        Apple a = new Apple( 100 );
        printPrice( a );
        
        Melon m = new Melon( 500, 100 );
        printPrice( m );
    }
    
}

念押しになりますが、『「Apple は Fruit」なので Fruit に代入できます』し、『「Melon は Fruit」なので Fruit に代入できます』ね。

 これで、「確実に」値段を表示する関数をつくれました。めでたしめでたし。

 ここで、なぜ Java は、「Apple または Melon」という書き方ではなくて「Fruit」という共通の名前を必要とするような文法になっているのか、を考えます。だいたい、新しく Fruit クラスを定義するなんて面倒くさいだけだと思いますよねぇ。



 では例を。
 
 Apple, Melon と来て、この流れで行くと、新しく Lemon クラスが必要になっちゃったりしそうです。そのとき、
 

printPrice( Apple または Melon または Lemon f )
にするほうが優れているのか、
 
printPrice( Fruit f )
にするほうが優れているのか、というと printPrice の書き換えが発生しないという点で後者の方が優れているのです。

 何故か。

 Apple, Melon, Lemon ときたら、このあといくらでも増えそうです。Fruit っていう名前を用意したことで、Grape, Banana, Strawberry, ... と増やしていく作業の見通しが立ちます。つまり

「共通の何か(Fruit)を作る、ということは、それを踏まえた具体的な何か(Apple, Melon, ...)を新しく作っていく上でのガイドラインになる」

のです。
 また一方で、printPrice だけではなく、もっと多彩な関数を作りたくなりそうです。例えば、かごに入っている Fruit の数を数えたり、その値段の合計を計算したり...。そうした関数を書く際に、もしもいちいち Apple ,.... Strawberry と書かないといけないのであれば非常に面倒だし、ヒューマンエラーが入りやすそうです。つまり、

「共通の何か(Fruit)であることだけを踏まえたコードが書ける、ということは、実際の具象物(Apple, Melon, ...)にどんなものがあるかを考慮しないというのに正しく書ける」

ということです。継承ってものの価値は、この2点を両立していることにあるのです。