///
このメモの方法だと問題がある事がわかり、
改訂版のメモを書きました。
[改訂]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