2012.06.27 | 

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