createjsの継承の実装について整理
javascriptで、オブジェクト指向の継承のようなものを実装する方法はいくつかあるようですが、今回のエントリーでは現在業務で使っているcreatejsのそれについて簡単に整理しましたので書いておこうと思います。
// ネームスペース // 名前空間の汚染を防ぐため this.namespace = this.namespace || {}; (function(){ // コンストラクタ関数 // new Cat() で呼び出される関数 /** * コンストラクタ */ var Cat = function() { // }; // static変数 // すべて大文字 // 外部から書き換えができてしまうが、すべて大文字であるため外部から書き換えをしないというルールで運用する /** 脚の数 */ Cat.LEGS_COUNT = 4; // 継承はプロトタイプに親のインスタンスをセットして行う // 継承するものがないときは // var p = Cat.prototype; /** プロトタイプ */ var p = Cat.prototype = new namespace.Animal(); // プライベート変数 // 先頭にアンダースコアをつける // 外部から書き換えができてしまうが、アンダースコアがついているため外部から書き換えをしないというルールで運用する /** 名前 */ p._name; // パブリック変数 /** 体重 */ p.weight = 0; // プライベートメソッド // 先頭にアンダースコアをつける // 外部から書き換えができてしまうが、アンダースコアがついているため外部から書き換えをしないというルールで運用する /** * うずくまる */ p._crouch = function(){ // }; // パブリックメソッド /** * 名前を取得する * @return 名前 */ p.getName = function() { return this._name; }; /** * 名前を設定する * @param name */ p.setName = function(name) { this._name = name; }; // オーバライド // オーバライドするときはそのまま定義する(この場合Animalのプロトタイプにwalkという関数が定義されているとする) /** * 歩く */ p.walk = function(){ // }; // オーバライド時にsuper.xxx()のように書きたいとき /** 眠る */ p.Animal_sleep = p.sleep; /** * 眠る */ p.sleep = function(){ this.Animal_sleep(); this._crouch(); }; // 外部からnamespace.Catでアクセスできるようにする namespace.Cat = Cat; })();
private扱いのものが外部からアクセスできないようにする実装することも可能なのでしょうが、javascriptの特性であるユルさがなくなってしまうような感じがするので自分はこのままでもいいのかなぁと思います。
ちなみに社内の叩き台で作成されたドキュメントに以下のような感じで書かれていたのですが、
/** 名前 */ var _name; /** * コンストラクタ */ var BadAnimal = function() { // }; var p = BadAnimal.prototype; p.getName = function() { return _name; }; p.setName = function(name) { _name = name; };
これだと"_name"がBadAnimalのプロトタイプ変数ではないので、
var animal1 = new namespace.BadAnimal(); var animal2 = new namespace.BadAnimal(); animal1.setName("iniesta"); console.log("animal1:" + animal1.getName()); console.log("animal2:" + animal2.getName()); animal2.setName("xavi"); console.log("animal1:" + animal1.getName()); console.log("animal2:" + animal2.getName());
としたときのログを見てみると
animal1:iniesta animal2:iniesta animal1:xavi animal2:xavi
となってしまいます。
これは完全にjavascriptでgetter/setterを実装してやろうとしたけれど失敗してしまったパターンですね。
以前どこかの勉強会で白石俊平さん(@Shumpei)が「"javascriptはつぎはぎだらけになってしまう"というよりも"つぎはぎだらけで書けてしまう"と考えるようにした」という話をされていたのですが、まさにそれと同様、無理をしてオブジェクト指向言語の実装を真似る必要はjavascriptにはないのだと個人的に思います。