UTALI

みんなの役に立つ情報をどんどん公開していきます

JavaScriptでのオブジェクト指向プログラミング - 継承・オーバーライド

JavaScriptのオブジェクト志向プログラミングはプロトタイプチェーンと呼ばれる仕組みで設計されています。これはC++やJavaなどの標準的なオブジェクト指向プログラミング言語からすると分かりにくいです。

今回はJavaScriptでのプロトタイプチェーンを使ったプログラミングの手法を解説した後で、ES6から導入されたJava形式のオブジェクト指向記法を紹介します。

最初に

JavaScriptでのオブジェクト指向プログラミングは不完全なものです。

標準的なオブジェクト指向言語に備わっているprivateやpublicなどのアクセス権限のための属性を指定することはできません(少なくとも現時点では)

そのほかにも、様々な制約があります。あまりJavaScriptに多くのことを期待しないでください。

コンストラクタ

JavaScriptでオブジェクトを定義する時には、コンストラクタとなる関数を定義し、その名前をオブジェクトと結びつけます。

この例はRPGのモンスターに対応するオブジェクトを定義します。コンストラクタはヒットポイントと攻撃力、防御力を初期値として与えるものです。

var Monster = function (hitpoint, strength, defense) {this.hitpoint = hitpoint;
     this.strength = strength;
     this.defense = defense;

}

この時点でMonsterオブジェクトが定義できました。試しに適当な初期値を与えて、各属性値にアクセスをしてみます。

var monster = new Monster(100, 10, 5);
console.log(monster.hitpoint);
console.log(monster.strength);
console.log(monster.defense);

ブラウザやREPLなどで結果を試してみれば、それぞれ対応する値を取得できるはずです。

残念ながら、もし直接アクセッサを利用して値を改変してほしくない場合もJavaScriptではprivateなどの属性値を利用して制限をすることはできません。

クロージャやプレフィックスを利用したテクニックがあるそうですが、Mozillaの公式ドキュメントでは非推奨であるとされています。

将来的にはこの部分も改善されるということを期待して、あまりトリッキーなことはするなということです。

メソッド

コンストラクタの定義が済んだら次は個別のメソッドを定義します。各メソッドはコンストラクタの要素であるprototypeの子要素として定義します。

例えば、先ほどのMonsterのヒットポイントを取得したり、新しいヒットポイントを定義するメソッドはそれぞれ以下のようになります。

Monster.prototype.getHitpoint = function () {return this.hitpoint;

}

Monster.prototype.setHitpoint = function (hitpoint) {this.hitpoint = hitpoint;

}

Monster.prototype.attack = function () {return this.strength;

}  

実際にこのメソッドを使ってみましょう。

var monster = new Monster(100, 10, 5);

console.log(monster.getHitpoint()); // 100
console.log(monster.setHitpoint(90)); // ここでヒットポイントを90に変更
console.log(monster.getHitpoint());  // 90

継承

継承は通常、継承先のコンストラクタの中でcallを使って、継承元のコンストラクタを呼び出してから、継承先で新しく定義するプロパティを設定します。もちろんコンストラクタには継承元から引き継ぐ値と新しく定義するもの、両方を引数としてください。

今回はモンスター(Monster)を基底クラスとして、魔女(Witch)を定義することにします。

var Witch = function(hitpoint, strength, magicpoint) {
    Monster.call(this, hitpoint, strength);
    this.magicpoint = magicpoint;
}

次に各メソッドを継承するために継承先のprototypeに継承元のインスタンスを代入します。

Witch.prototype = new Monster();

これでMonsterで定義したメソッドが継承元のWitchでも利用できるようになりました。

継承先で新しくメソッドを定義する

あとは通常通り新しくメソッドを継承することができます。


Witch.prototype.healing = function () {this.magicpoint -= 2;
   this.hitpoint += 10;

}

オーバーライド

メソッドのオーバーライドは簡単で、継承先のprototypeに直接対応する新しいメソッドを書き込めば良いだけです。


Witch.prototype.attack = function () {
    return this.strength * 0.5;  
}

ES6でのオブジェクト指向プログラミング

従来のJavaScriptではプロトタイプチェーンを使ってメソッドの定義・継承を実行していました。しかしこの手法はJavaなどのオブジェクト指向言語に慣れていると分かりにくいと非常に評判が悪かったのも事実でした。

JavaScriptの新規格であるES6(ECMA Script 2015)ではJavaやC++などの一般的なオブジェクト指向言語に慣れたプログラマーにも分かりやすい記法を導入しています。今回はその定義・継承を中心に紹介してみようと思います。


基本的な記法はJava言語での記法に似たものになっています。

宣言

ES6ではclassに続いてオブジェクト名を明示することでオブジェクトを宣言します。 この例ではMonsterをクラス名としています。

class Monster {

}

コンストラクタ

ES6でプロパティの初期値を設定する場合はconstructor関数を利用します。constructor関数はインスタンスの確保時に実行され渡された引数をクラス内変数にセットします。

class Monster {
    constructor(hitpoint, strength) {
        this.hitpoint = hitpoint;
        this.strength = strength;
    }
}

コンストラクタには初期値を設定することも可能です。この場合引数が渡されなかった場合に、ageに20が代入されます。

class Monster {
    constructor(hitpoint = 100, strength = 20) {
        this.hitpoint = hitpoint;
        this.strength = strength;
    }
}

メソッド

メソッドは通常のJavaScriptの関数と異なる記法で宣言します。functionや=>は利用しないことに注意してください。基本的に関数名のみを指定してメソッドの定義を行います。 また、メソッド内でクラス内変数を呼び出す場合はthisの要素として呼び出してください。

メソッドを定義したスコープの後にコンマは置きません。

class Monster {
    constructor(hitpoint = 100, strength = 20) {
        this.hitpoint = hitpoint;
        this.strength = strength;
    }
    getDamage (damage) {this.hitpoint = this.hitpoint - damage;
        console.log("now your HP is " + this.hitpoint);
    
    }
    attack () {return this.strength;
    
    }
}

継承

あるクラスを継承して新しくクラスを宣言する場合はextendsで継承元のクラスを指定して元のクラスのメソッドを利用することができます。 


class Witch extends Monster{

}

また、継承元のコンストラクタを呼び出す場合は、継承先のコンストラクタ内で、super()を利用します。

class Witch extends Monster{
    constructor(hitpoint = 100, strength = 10, magicpoint = 20) {
        super(hitpoint, strength);
        this.magicpoint = magicpoint;
    }
}

オーバーライド

メソッドのオーバーライドに関しては明示的に宣言するプレフィックスが存在するわけではなく、単純に継承先のクラス内で再宣言を行えばよいだけです。

class Witch extends Monster{
    constructor(hitpoint = 100, strength = 10, magicpoint = 20) {
        super(hitpoint, strength);
        this.magicpoint = magicpoint;
    }
    // 元のattackをoverrideしている。
    attack () {return this.strength - 1;

    }
}

継承元のクラスのメソッドをオーバーライドしたメソッド内で利用することもできます。これを実行するにはsuperのメソッドとして呼び出すことで実行することができます。

class Witch extends Monster{
    constructor(hitpoint = 100, strength = 10, magicpoint = 20) {
        super(hitpoint, strength);
        this.magicpoint = magicpoint;
    }
    // 元のattackをoverrideしている。
    attack () {
        // 継承元のクラスのattackを呼び出している。
        super.attack()

        return this.strength - 1;

    }
}

また、単純にスーパークラスのメソッドをそのまま呼び出す場合は、継承先のクラス内で再定義などをする必要はありません。 


インスタンスの作成

インスタンスの作成は従来と同じでnewを使用して宣言を行います。

var monster = new Monster(50,10);
let e = new Witch();
e.getDamage(10); 

結果

now your HP is 90

糖衣構文

現時点でのclassを利用したオブジェクト指向プログラミングははプロトタイプチェーンの糖衣構文に過ぎないことに注意してください。つまり、内部では従来のプロトタイプチェーンと同じように扱われるということです。