///
このメモの方法だと問題がある事がわかり、
改訂版のメモを書きました。
[改訂]CoffeeScriptでstatic/private/publicなメンバ/メソッドをもったクラスのつくりかた
以下のメモを読んでから改訂版に飛ぶもよし、すぐに改訂版に飛ぶもよし。
///
JavaScriptを勉強するのを避け
CoffeeScriptを今更勉強しているいじってる。
※勉強しているレベルではなかった事が今回判明。知らないこと大杉(汗
アルミカン氏の
JavaScriptでstatic/private/publicなメンバ/メソッドをもったクラスのつくりかた
を読んで、
おろ?Coffeeでなんとなくクラス使ってたが、
吐き出されたコードどうなんだぃ?
おもむろにCoffeeでクラスを書き始めるが・・・
あれ、privateな変数とメソッドはどう書いたらいいんだべ?
CoffeeScriptファーストガイドなる本をペラペラめくって
クラスの変数とメソッドのあたりをまとめてみた。
class ClassName constructor: -> # インスタンスのプロパティ @publicVar = 0 # プライベートな変数 _privateVar = 0 # プライベートな関数 _privateFunction = -> # インスタンスのプロパティ publicVar2: 0 # 公開メソッド publicFunction: -> # 静的なプロパティ @staticVar: 0
用語に統一感なさすぎw
書き出されたソースを元に
static/private/public/var/function
のキーワードを使った表現に統一して書き直してみる。
class ClassName constructor: -> # public var @publicVar = 0 # static private var _privateVar = 0 # static private function _privateFunction = -> # public var publicVar2: 0 # public function publicFunction: -> # static public var @staticVar: 0
ん〜。スッキリ。
はにゃ?
結局、privateな変数とメソッドないやん。
ソースを読んで整理してみたら、
本に書いてあった「プライベートな変数」と「プライベートな関数」は
「静的な」が抜けていることがわかった。
色々調べた結果、
privateにするには、メソッド内のスコープに押し込めるしかなさそうだとわかった。
要するに、アルミカン氏と同じ方法という事だね。
CoffeeScriptのクラスでややこしいのが、
publicな変数やメソッドを定義する方法が2つあること。
例えばconstructor内のスコープに
privateな変数やメソッドを定義しても、
prototypeなメソッドからはアクセスできません。
class ClassName constructor: -> # private var (クロージャを使った実装) _privateVar = 0 # private function (クロージャを使った実装) _privateFunction = -> #アクセスできる _privateVar #アクセスできない _privateVar2 # public var (クロージャを使った実装) @publicVar = 0 # public function (クロージャを使った実装) @publicFunction = -> #アクセスできる _privateVar #アクセスできない _privateVar2 # public var (prototypeを使った実装) publicVar2: 0 # public function (prototypeを使った実装) publicFunction2: -> # もちろん、このスコープ内にもprivateな変数やメソッドを定義できる # private var (クロージャを使った実装) _privateVar2 = 0 # private function (クロージャを使った実装) _privateFunction2 = -> #アクセスできない _privateVar #アクセスできる _privateVar2 #アクセスできない _privateVar
クロージャを使った実装のメリットは、
計算量が多い時は微妙に速いらしいということと、
コードが統一されて読みやすいところ。
prototypeを使った実装のメリットは、
メモリを節約できることと、
記述がCoffeeらしいこと。
というわけで、色々なところでprivate使うとややこしくなりそうなので、
private使うならconstructorにまとめるのが良さそう。
いっそのこと、staticでないものはconstructorにまとめるのが良いかもしれない。
なれたら、混ざっても気にならないのかな??
あと、色々調べていて気づいた注意点があった。
staticも含むprivateな変数の定義はなるだけ始めに済ます。
CoffeeScriptはvarキーワードを使わないのです。
使ってない変数に何かを代入しようとすると、
自動で宣言文をスコープ内に生成してくれるルールなので、
privateな変数を定義前にメソッドの中で使ってしまうと、
メソッドのスコープ内に同名の変数ができてしまいます。
class ClassName constructor: -> _privateFunction = -> _privateVar = 2 _privateVar2 = 2 _privateVar = 1 _privateVar2 = 1 _privateVar2 = 0
上のCoffeeをパブリッシュすると下のjsを吐く。
var ClassName;
ClassName = (function() {
var _privateVar2;
function ClassName() {
var _privateFunction, _privateVar, _privateVar2;
_privateFunction = function() {
var _privateVar, _privateVar2;
_privateVar = 2;
return _privateVar2 = 2;
};
_privateVar = 1;
_privateVar2 = 1;
}
_privateVar2 = 0;
return ClassName;
})();
さて、最後にアルミカン氏のサンプルをCoffeeScriptで書いたらどうなるかを晒して終わりにします。
本当は出来上がりの違いを楽しみたいところだけど、最終的に同じようなコード吐いて終わった(汗
あまりにも残念なので継承も追加したサンプルにした。
注意すべき挙動は、staticな変数やメソッドも継承される。
テスト結果のcountとsemanticNameに注目してみてください。
//吐き出したjsそのまま貼ったので、コメント位置やインデントが残念になってるのは見逃して〜。あと、妙な改行w
class Animal ### ---------------------------------------- PRIVATE STATIC MEMBER ---------------------------------------- ### _count = 0 ### ---------------------------------------- PRIVATE STATIC METHOD ---------------------------------------- ### _inclement = -> ++_count ### ---------------------------------------- PUBLIC STATIC MEMBER ---------------------------------------- ### @semanticName: 'Unknown' ### ---------------------------------------- PUBLIC STATIC METHOD ---------------------------------------- ### @getCount: -> _count constructor: (sex)-> ### ---------------------------------------- PRIVATE INSTANCE MEMBER ---------------------------------------- ### _age = 0 _sex = sex ### ---------------------------------------- PRIVATE INSTANCE METHOD ---------------------------------------- ### _aging = -> ++_age; ### ---------------------------------------- PUBLIC INSTANCE MEMBER ---------------------------------------- ### @name = '' ### ---------------------------------------- PUBLIC INSTANCE METHOD ---------------------------------------- ### @getAge = -> _age @getSex = -> _sex @grow = -> _aging() ### call private static method ### _inclement(); class Cat extends Animal ### ---------------------------------------- test ### console.log 'Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName Animal.semanticName = 'Dog' john = new Animal 'male' john.name = 'John' john.grow() john.grow() john.grow() console.log john.name + ' : ' + 'sex = ' + john.getSex() + ', age = ' + john.getAge() console.log 'Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName kacy = new Animal 'female' kacy.name = 'Kacy' kacy.grow() kacy.grow() console.log kacy.name + ' : ' + 'sex = ' + kacy.getSex() + ', age = ' + kacy.getAge() console.log 'Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName console.log '====================' console.log 'Cat : count = ' + Cat.getCount() + ', semanticName = ' + Cat.semanticName Cat.semanticName = 'Cat' mike = new Cat 'male' mike.name = 'Mike' mike.grow() mike.grow() mike.grow() mike.grow() console.log mike.name + ' : ' + 'sex = ' + mike.getSex() + ', age = ' + mike.getAge() console.log 'Cat : count = ' + Cat.getCount() + ', semanticName = ' + Cat.semanticName console.log 'Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName
上のCoffeeをパブリッシュすると下のjsを吐く。
// Generated by CoffeeScript 1.3.3
var Animal, Cat, john, kacy, mike,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Animal = (function() {
/*
----------------------------------------
PRIVATE STATIC MEMBER
----------------------------------------
*/
var _count, _inclement;
_count = 0;
/*
----------------------------------------
PRIVATE STATIC METHOD
----------------------------------------
*/
_inclement = function() {
return ++_count;
};
/*
----------------------------------------
PUBLIC STATIC MEMBER
----------------------------------------
*/
Animal.semanticName = 'Unknown';
/*
----------------------------------------
PUBLIC STATIC METHOD
----------------------------------------
*/
Animal.getCount = function() {
return _count;
};
function Animal(sex) {
/*
----------------------------------------
PRIVATE INSTANCE MEMBER
----------------------------------------
*/
var _age, _aging, _sex;
_age = 0;
_sex = sex;
/*
----------------------------------------
PRIVATE INSTANCE METHOD
----------------------------------------
*/
_aging = function() {
return ++_age;
};
/*
----------------------------------------
PUBLIC INSTANCE MEMBER
----------------------------------------
*/
this.name = '';
/*
----------------------------------------
PUBLIC INSTANCE METHOD
----------------------------------------
*/
this.getAge = function() {
return _age;
};
this.getSex = function() {
return _sex;
};
this.grow = function() {
return _aging();
};
/*
call private static method
*/
_inclement();
}
return Animal;
})();
Cat = (function(_super) {
__extends(Cat, _super);
function Cat() {
return Cat.__super__.constructor.apply(this, arguments);
}
return Cat;
})(Animal);
/*
----------------------------------------
test
*/
console.log('Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName);
Animal.semanticName = 'Dog';
john = new Animal('male');
john.name = 'John';
john.grow();
john.grow();
john.grow();
console.log(john.name + ' : ' + 'sex = ' + john.getSex() + ', age = ' + john.getAge());
console.log('Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName);
kacy = new Animal('female');
kacy.name = 'Kacy';
kacy.grow();
kacy.grow();
console.log(kacy.name + ' : ' + 'sex = ' + kacy.getSex() + ', age = ' + kacy.getAge());
console.log('Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName);
console.log('====================');
console.log('Cat : count = ' + Cat.getCount() + ', semanticName = ' + Cat.semanticName);
Cat.semanticName = 'Cat';
mike = new Cat('male');
mike.name = 'Mike';
mike.grow();
mike.grow();
mike.grow();
mike.grow();
console.log(mike.name + ' : ' + 'sex = ' + mike.getSex() + ', age = ' + mike.getAge());
console.log('Cat : count = ' + Cat.getCount() + ', semanticName = ' + Cat.semanticName);
console.log('Animal : count = ' + Animal.getCount() + ', semanticName = ' + Animal.semanticName);
Animal : count = 0, semanticName = Unknown John : sex = male, age = 3 Animal : count = 1, semanticName = Dog Kacy : sex = female, age = 2 Animal : count = 2, semanticName = Dog ==================== Cat : count = 2, semanticName = Unknown Mike : sex = male, age = 4 Cat : count = 3, semanticName = Cat Animal : count = 3, semanticName = Dog

