JavaScriptでクラスを定義する

擬似的な抽象クラス、継承関係を持つクラスを定義する方法。

抽象クラスAnimalを定義

/**
 * 動物クラス。
 * 抽象クラスとして用いる。
 */
var Animal = (
    function() {
        // コンストラクタ
        var Animal = function(name) {
            if (!(this instanceof Animal)) {
                return new Animal(name);
            }

            // インスタンス変数。
            // JavaScriptはprivate, protectedスコープが存在しないので、"_"を付け、privateスコープ変数を表現する。
            // 外部からアクセス可能だが、アクセスしてはならない。
            this._name = name;

            // 自インスタンスがAnimalクラスのインスタンスだった場合、インスタンスを生成できないよう、エラーとする。
            if (this.constructor.name === 'Animal') {
                throw new Error("Could not create Animal class instance. Cause: Animal class is abstract class.");
            }
        }

        // プロトタイプを取得
        var prototype = Animal.prototype;

        // 名前を出力する。
        prototype.printName = function() {
            console.log("名前は " + this._name + " です。");
        }

        // 鳴き声を取得する。擬似的な抽象メソッド。サブクラスでオーバーライドして鳴き声を実装する。
        // サブクラスでオーバーライドしなかった場合、オーバーライドを強制する為に、エラーを発生させる。
        prototype.getSound = function() {
            throw new Error("'getSound' method must be overridden at " + this.constructor.name + " class.");
        }

        // 鳴く。
        prototype.say = function() {
            console.log(this.getSound());
        }

        return Animal;
    }       
)();

いくつか解説します。

9行目:自インスタンスがAnimalクラスのインスタンスでない場合、自インスタンスを生成します。これはnewを付けずにAnimal()関数を実行した場合でも、インスタンスを生成させる為に行っている処理です。誤ってnew を付けずにコンストラクタを実行した際の事故防止です。

19行目:自インスタンスがAnimalクラスのインスタンスであった場合、例外を投げています。これは、Animalクラスを抽象クラスとして運用したいので、インスタンスを生成させたくない為に行っています。

34行目:getSound() メソッド実行時に例外を投げています。これは、サブクラスでこのクラスをオーバーライドしていなかった場合に、オーバーライドを強制させる為に行っています。

具象クラスCatを定義

/**
 * 猫クラス。
 * 動物クラスを継承。
 */
var Cat = (
    function() {
        // 鳴き声を表すクラス内定数
        const MEOW = "ミャー";

        // コンストラクタ
        var Cat = function(name) {
            if (!(this instanceof Cat)) {
                return new Cat(name);
            }

            // スーパークラスのコンストラクタ呼び出し
            Animal.call(this, name);
        }

        // Animalクラスを継承する
        Object.setPrototypeOf(Cat.prototype, Animal.prototype);

        // プロトタイプを取得
        var prototype = Cat.prototype;

        // 鳴き声を返す。スーパークラスAnimalのメソッドをオーバーライド
        prototype.getSound = function() {
            return MEOW;
        }

        return Cat;
    }
)();

17行目:スーパークラス Animalのコンストラクタを実行しています。余談ですが、スーパークラスのメソッドを実行するには以下のように記述します。

<SuperClass>.prototype.<MethodName>.call(this<, arg>);

21行目:サブクラスCatのprototypeにスーパークラスAnimalを設定し、継承関係を構築します。

具象クラスDogを定義

/**
 * 犬クラス。
 * 動物クラスを継承。
 */
var Dog = (
    function() {
        // 鳴き声を表すクラス内定数
        const BOW = "ワン";

        // コンストラクタ
        var Dog = function(name) {
            if (!(this instanceof Dog)) {
                return new Dog(name);
            }

            // 親クラスのコンストラクタ呼び出し
            Animal.call(this, name);
        }

        // Animalクラスを継承する
        Object.setPrototypeOf(Dog.prototype, Animal.prototype);

        // プロトタイプを取得
        var prototype = Dog.prototype;

        // 鳴き声を返す。スーパークラスAnimalのメソッドをオーバーライド
        prototype.getSound = function() {
            return BOW;
        }

        return Dog;
    }
)();

Catクラスと同様。

具象クラスHorseを定義

/**
 * 馬クラス。
 * 動物クラスを継承。意図的にgetSound()メソッドをオーバーライドしない。
 * よって、say()メソッドを実行した際にエラーとなる。その実証用クラス。
 */
var Horse = (
    function() {
        // 鳴き声を表すクラス内定数
        const NEIGH = "ヒヒーン";

        // コンストラクタ
        var Horse = function(name) {
            if (!(this instanceof Horse)) {
                return new Horse(name);
            }

            // 親クラスのコンストラクタ呼び出し
            Animal.call(this, name);
        }

        // Animalクラスを継承する
        Object.setPrototypeOf(Horse.prototype, Animal.prototype);

        // プロトタイプを取得
        var prototype = Horse.prototype;

        // 鳴き声を返す。スーパークラスAnimalのメソッドをオーバーライド
        // 意図的にオーバーライドせずにエラーを発生させる。
        // prototype.getSound = function() {
        //      return NEIGH;
        // }

        return Horse;
    }
)();

29行目:意図的にgetSound()メソッドをオーバーライドしていません。実行時に例外エラーとなります。

テスト

正常系。

var animalArray = [];
// 猫クラスのインスタンス生成
animalArray.push(new Cat("タマ"));
// 犬クラスのインスタンス生成
animalArray.push(new Dog("ポチ"));

for(var i = 0; i < animalArray.length; i++) {
    var animal = animalArray[i];
    animal.printName();
    animal.say();
}

実行結果。

異常系。

Horseクラスのインスタンスを生成します。

var animalArray = [];
// 猫クラスのインスタンス生成
animalArray.push(new Cat("タマ"));
// 犬クラスのインスタンス生成
animalArray.push(new Dog("ポチ"));
// 馬クラスのインスタンス生成 say()メソッド実行時にエラーとなる。
animalArray.push(new Horse("ディープインパクト"));

for(var i = 0; i < animalArray.length; i++) {
    var animal = animalArray[i];
    animal.printName();
    animal.say();
}

実行結果。

HorseクラスではgetSound()メソッドを意図的にオーバーライドしていませんでした。よって、AnimalクラスのgetSound()メソッドに実装した例外生成処理が行われ、エラーとなっています。

Animalクラスのインスタンスを生成します。

var animalArray = [];
// 猫クラスのインスタンス生成
animalArray.push(new Cat("タマ"));
// 犬クラスのインスタンス生成
animalArray.push(new Dog("ポチ"));
// 馬クラスのインスタンス生成 say()メソッド実行時にエラーとなる。
animalArray.push(new Horse("ディープインパクト"));
// 動物クラスのインスタンス生成 エラーが発生する。
animalArray.push(new Animal("foobar"));

for(var i = 0; i < animalArray.length; i++) {
    var animal = animalArray[i];
    animal.printName();
    animal.say();
}

実行結果。

Animalクラスのコンストラクタでインスタンスのクラスを判定し、Animalクラスのインスタンスであれば例外を生成するように実装しています。よって、例外生成処理が行われ、エラーとなっています。

最後に

このような形で擬似的な抽象クラス、継承関係を持つクラスの実装を行えます。あくまで一例です。他にも良い実装方法があります。興味が湧いたら調べてみると良いでしょう。

今回作成したファイルへのリンクです。必要に応じてダウンロードして動かしてみて下さい。