Section 2.6 継承

今回はクラスの重要な機能、継承について。
けいしょう?
継承っていうのは、あるクラスのメンバ変数・メソッド・メンバプロパティを全部引き継いで新しいクラスを作ることだよ。
えっと、具体的にはどういうことなの?
じゃあ、実際に継承してみるね。

<継承の例:Input クラス(スーパークラス)の定義>

class Input
{
    // メンバ変数 prevInput は、前回入力された内容を保持します
    var prevInput;

    // コンストラクタ
    function Input()
    {
        prevInput = "";  // prevInput を空文字列に初期化します
    }

    // デストラクタは何もしません
    function finalize(){}

    // 入力を求めるウィンドウを表示します
    // OK ボタンが押されると真、キャンセルされると偽を返します
    // 引数はすべて省略可能で、デフォルト値は以下の通りです
    // 第1引数:空文字列
    // 第2引数:"値を入力してください。"
    // 第3引数:前回入力した値(初回は空文字列)
    function getInput(caption = "", prompt = "値を入力してください。", initialString = prevInput)
    {
        var str = System.inputString(caption, prompt, initialString);
        if(str === void)
            return false;  // キャンセルされた場合は偽を返します

        // 値が入力された場合は prevInput に保存して真を返します
        prevInput = str;
        return true;
    }

    // previous プロパティ(読み出し専用):前回入力された文字列を返します
    property previous
    {
        getter()
        {
            return prevInput;
        }
    }
}

これが継承元になる Input クラスの定義ね。
継承元って?
継承元になる Input クラスを継承して新しいクラスを作ると、そのクラスは Input クラスのメンバ、 つまり prevInput メンバ変数と getInput メソッド、それから previous プロパティが定義しなくても使えるようになるんだ。
へぇ…そうなんだ。
で、継承元のクラスのことをスーパークラスって呼ぶの。
あと「基底クラス」「基本クラス」「親クラス」って言ったりもするんだけど、ここでは「スーパークラス」って呼ぶことにするね。
うん、わかった。スーパークラスね。
じゃあ、getInput メソッドの動作は解る?
えっと、まず inputString メソッドを呼び出してるね。
うん。
それで、inputString メソッドの戻り値が void だったら何もせずに false を返して、 void じゃなかったら、その戻り値を prevInput っていうメンバ変数に代入して true を返すんだよね。
ん、そう。
つまり、OK ボタンが押されたら真を返して、キャンセルボタンが押されたら偽を返すってことだね。
あと、getInput メソッドは引数を省略できるようになってるよ。
getInput メソッドの引数はそのまま inputString メソッドに渡されてるから、 第1引数が入力ウィンドウのタイトルに表示される文字列で、省略すると空文字列になるんだよね。
うんうん。
第2引数は文字を入力するボックスの上に表示される文字列で、省略すると "値を入力してください。" っていう文字列になるよね。
そうそう。
じゃあ第3引数は?
第3引数のデフォルト値って、メンバ変数の prevInput なの?
そ。デフォルト値にはメンバ変数も指定できるからね。
prevInput は前回入力された内容を記憶しておく変数だから…
第3引数を省略すると、前回入力された文字列が最初から表示されてるってこと?
そゆこと。
ちなみに初めて入力ウィンドウを表示するときは、第3引数を省略するとデフォルト値が空文字列になるから、何も入力されてない状態になるよ。
なるほどね。
あと previous プロパティも見とこっか。
previous プロパティはゲッターしかないから読み出し専用で、 prevInput の値をそのまま返してるね。
ん、そう。
つまり previous プロパティは前回入力された文字列を取得できるプロパティ。
うん。
これで Input クラスは解ったよ。
じゃあ、今度は Input クラスを継承して新しいクラスを作ってみるね。

<継承の例:Input クラスを継承して作った Calculator2 クラス>

class Calculator2 extends Input
{
    var ans;  // 計算結果を格納するメンバ変数

    // コンストラクタ
    function Calculator2()
    {
        super.Input();  // スーパークラスのコンストラクタを呼び出します

        ans = 0;  // メンバ変数を初期化します
    }

    // デストラクタ
    function finalize()
    {
        super.finalize();  // スーパークラスのデストラクタを呼び出します
    }

    // 計算式の入力を求めるメソッド
    // OK ボタンが押されると式の計算をした後 true、キャンセルボタンが押されると false を返します
    function calculate()
    {
        // スーパークラスのメソッドを呼び出します
        if(!getInput("電卓""計算式を入力してください。"))
            return false;  // キャンセルされたので false を返します

        answer = prevInput!;  // スーパークラスのメンバ変数を参照して、入力された式の値を計算します

        return true;
    }

    // メンバ変数 ans を操作するためのメンバプロパティ
    property answer
    {
        // セッター:引数が整数型か実数型なら ans に代入します
        setter(value)
        {
            if((typeof value == "Integer") || (typeof value == "Real"))
                ans = value;
            else
                System.inform("メンバ変数 ans に不正な値を代入しようとしました。""エラー");
        }

        // ゲッター:ans の値を返します
        getter()
        {
            return ans;
        }
    }
}  // Calculator2 クラスの定義はここまで


// Calculator2 クラスの使用例
var calc = new Calculator2();

while(calc.calculate())  // キャンセルボタンが押されるまで繰り返します
    System.inform(calc.answer, "計算結果");

invalidate calc;

これって前回の電卓クラス
機能的には、入力ウィンドウに前回入力した式が表示されること以外 Calculator クラスと同じだね。
でも Calculator2 クラスは Input クラスを継承して作ってるから、中身は違ってるよ。
確かに calculate メソッドの中身が前と違ってるね。
うん。スーパークラスのメソッドとメンバ変数を使ってるからね。
で、継承の仕方なんだけど、extends っていうキーワードを使うんだ。
それって、クラス名の後に書いてある『extends Input』のこと?
うん、『class Calculator2 extends Input』っていうのは、 『Input クラスを継承して Calculator2 クラスを作るよ』って意味。
継承する場合は、こんなふうにクラスを定義するんだ。

<継承を行う場合のクラス定義>

class サブクラス名 extends スーパークラス名
{
    // コンストラクタ
    function サブクラス名(引数)
    {
        super.スーパークラス名(引数);  // スーパークラスのコンストラクタを呼び出します
        // サブクラスのコンストラクタ処理
    }

    // デストラクタ
    function finalize()
    {
        // サブクラスのデストラクタ処理
        super.finalize();  // スーパークラスのデストラクタを呼び出します
    }

    // サブクラスのメンバ変数宣言・メソッドの定義・プロパティの定義(サブクラス定義内の任意の位置に書けます)
}

継承先のクラスはサブクラスって言うの。
他にも「派生クラス」とか「子クラス」って呼ばれることもあるよ。
スーパークラスもそうだけど、こっちも色んな呼び方があるんだね。
うん。継承元をスーパークラスって呼ぶ場合は、継承先はサブクラスって呼ぶのが普通だから、 ここではサブクラスって呼ぶことにするね。
りょーかい。
サブクラスを作る時に注意しなくちゃいけないのが、サブクラスのコンストラクタとデストラクタでは、 それぞれスーパークラスのコンストラクタとデストラクタを呼び出さなくちゃいけないってこと。
それって『super.Calculate();』っていうのと『super.finalize();』っていうののこと?
うん、そう。
super っていうキーワードを使うと、スーパークラスのメンバ変数・メソッド・プロパティが使えるんだ。
じゃあ、『super.Calculate();』がスーパークラスのコンストラクタを呼び出してて、 『super.finalize();』がスーパークラスのデストラクタを呼び出してるってこと?
ん、そういうこと。
サブクラスを作る時にはこれを忘れないでね。
うん、わかった。
んじゃ次は calculate 関数を見てみよっか。
前回は最初に inputString メソッドを呼び出してたけど、今回は getInput メソッドを呼び出してるね。
うん。さっきも言ったように、サブクラスからはスーパークラスのメソッドが使えるから、 getInput メソッドは Calculator2 クラスの中で定義しなくても使えるんだ。
なるほどね〜。
あ、でもさっきスーパークラスのメソッドを使うときは super を使うって言ってたよね?
getInput メソッドを呼び出してるところには super って書いてないよ?
サブクラスに同じ名前のメソッドがない時は super は書かなくてもいいんだ。
あ、そうなんだ。
メンバ変数の prevInput も、 サブクラスに同じ名前のメンバ変数がないから super はつけてないでしょ。
ほんとだ。
後は、前回exprprevInput に変わってるだけだから、 これで calculate 関数は解ったよね?
うん、おっけーだよ。
じゃあ最後は answer プロパティなんだけど、これはスーパークラスの要素を使ってないから説明しなくても大丈夫だよね。
うん。answer プロパティは前と変わってないもんね。
これで継承の事は大体解った?
うん、一応解ったんだけど…
何で Calculator2 クラスを作るのにわざわざ Input クラスを継承するの?
Input クラスに電卓の機能を追加して作ればいいと思うんだけど…
あー、それはこういう時のためだよ。

<もう一つのサブクラス:Time クラス>

class Time extends Input
{
    // コンストラクタ
    function Time()
    {
        super.Input();  // スーパークラスのコンストラクタを呼び出します
    }

    // デストラクタ
    function finalize()
    {
        super.finalize();  // スーパークラスのデストラクタを呼び出します
    }

    // 数値が入力された場合は、今からその数値分後の時刻を表示します
    // OK ボタンが押された場合は true、キャンセルされた場合は false を返します
    function displayTime()
    {
        if(!getInput("時刻表示""何分後?"))
            return false;

        var d = new Date();
        var hour = d.getHours();
        var min = d.getMinutes() + +prevInput;
        hour += min \ 60;
        min %= 60;  
        hour %= 24;  
        System.inform("今から " + +prevInput + " 分後は " + hour + " 時 " + min + " 分です。");
        return true;
    }
}  // Time クラスの定義はここまで


// Time クラスの使用例
var time = new Time();

while(time.displayTime());  // キャンセルボタンが押されるまで繰り返します

invalidate time;

これって、今から何分後かを表示するあれだよね?
そう、それ。前に作ったのよりは単純にしてるけどね。
こんなふうに Input クラスを継承して別のクラスも作れるんだ。
そっか。これも入力ウィンドウ表示するもんね。
Input クラスに機能を追加して Calculator2 クラスを作るんだったら、 Time クラスも同じように Input クラスに機能を追加して作らなくちゃいけないから、 Input クラスの部分を2回書かなくちゃいけなくなるでしょ。
あ、確かにそうだよね。
Input クラスは単純だから2回書いてもそんなに変わんないかもしれないけど、 もっと複雑なクラスだったら何回も同じことを書くのは無駄でしょ。
だから同じ部分はクラスにして共有するってこと?
うん、そういうこと。
なるほどね〜、だから継承するんだね。
継承を上手く使ったクラスが作れると効率的なスクリプトが書けるし、 KAG 用のプラグインとか作る時にも継承を使うから、継承は頑張ってマスターしてね。
う〜ん、でもちょっと難しそう…
まぁ、色々スクリプトを書いてれば慣れるよ、きっと。
そうかなぁ…?
それに、これから先も継承は結構使うしね。
わかった、がんばるね!
うん。
じゃあ、今回はここまで。次回からはしばらく継承関係をやっていくね。
それじゃ、また次回!


前へ | TOP | 次へ