2012.07.06 | 

どうも、kaminalyです。
「てめぇの馬鹿さ加減にはなぁ、父ちゃん情けなくて涙が出てくらぁ」
ということがあったので訂正します。

この前、「CoffeeScriptでstatic/private/publicなメンバ/メソッドをもったクラスのつくりかた」
というメモを書いたのですが、
最終的に落ち着いた方法だと、片手落ちというか、かなり問題があることがわかったので、
再度メモします。

前回は、最終的に
コンストラクタのスコープ内に押し込める形で
privateな変数/メソッドを表現するのがベストなのかなぁという感じで終わりました。

こんな感じです。


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();


でも、この方法だと問題がありました。
コンストラクタ内でpublicなメソッドを定義した場合、
継承した時のオーバーライドが不完全になります。



class NgSuper1
  constructor: ->
    @publicFunction = =>
      console.log "super"

class NgSub1 extends NgSuper1
  constructor: ->
    @publicFunction = =>
      console.log "sub"
      super()

#-------------------------------------

class NgSuper2
  constructor: ->
    @publicFunction = =>
      console.log "super"

class NgSub2 extends NgSuper2
  publicFunction: =>
    console.log "sub"
    super()

#-------------------------------------

class NgSuper3
  constructor: ->

  publicFunction: =>
    console.log "super"

class NgSub3 extends NgSuper3
  constructor: ->
    @publicFunction = =>
      console.log "sub"
      super()

#-------------------------------------

class OkSuper
  constructor: ->

  publicFunction: =>
    console.log "super"

class OkSub extends OkSuper
  publicFunction: =>
    console.log "sub"
    super()


それぞれのサブクラスをnewしてpublicFunctionを実行したら
sub
super
と表示するのが望ましい結果なんだが、
以下が実際の結果。


//////
NG1
sub
ReferenceError: publicFunction is not defined

//////
NG2
super

//////
NG3
sub
ReferenceError: publicFunction is not defined

//////
OK
sub
super


という感じ。
両方prototypeベースでないと、
オーバーライド出来なかったり、
super()が使えなかったりする仕様のようです。

ややこしいので、publicはprototypeベースになる記述方法にする決まりにするとなると、
コンストラクタのスコープ内に押し込めたprivateな変数やらメソッドは呼べなくなる。
もはや、privateなんてなかった事にして実装するしかないのか・・・
protectedも使えないし、やっぱり、疑似的なものでしかないんだなぁ。
継承のないクラスだとなんとか表現できてるんだけど。

色々しらべてたら、CoffeeScriptのprivateについては
GitHubのissueで結構前から色々議論されている様子。



class Person
  `function __p(t,s){return(t.__P1||(s={},t.__P1=function(f){return(f===__p)?s:{}}))(__p)}`

  constructor: (name) ->
    __p(this).name = name

  say: (what) ->
    console.log "#{what}, says #{__p(this).name}"

  meet: (other) ->
    console.log "#{__p(this).name} meets #{__p(other).name}"

class Contact extends Person
  `function __p(t,s){return(t.__P2||(s={},t.__P2=function(f){return(f===__p)?s:{}}))(__p)}`

  constructor: (name, phoneNumber) ->
    super name
    __p(this).phoneNumber = phoneNumber

  dial: () ->
    console.log "Calling #{__p(this).phoneNumber}"
    console.log "Private name: #{__p(this).name}"

bob = new Person 'Bob'
harry = new Contact 'Harry', '12341234'
harry.dial()
harry.say 'Hi'
bob.say 'Hello'
bob.meet harry

### output:
Calling 12341234
Private name: undefined
Hi, says Harry
Hello, says Bob
Bob meets Harry
###

#-別の実装---------------------------
id = 0
privatize = (cls) ->
  _ = {}
  register = (obj) ->
    id += 1 if "#{obj}" != "#{id}"
    strid = "#{id}"
    obj.toString = ->
      return strid
    _[strid] = {}
  [register, _]

class Foo
  [ register, _ ] = privatize @

  constructor: (name) ->
    register @
    _[@].name = name

  sayHi: ->
    #console.log "kita"
    console.log "foo says Hi #{_[@].name}" 

  reportPrivate: ->
    console.log _

class Boo extends Foo
  [ register, _ ] = privatize @

  constructor: (name) ->
    super()
    register @
    _[@].name = name

  booSayHi: ->
    console.log "boo says Hi #{_[@].name}" 

  reportPrivate: ->
    console.log _
    #console.log "hoge"

a = new Foo "Alice"
b = new Foo "Bob"
c = new Boo "Jane"

a.sayHi()
b.sayHi()
c.sayHi()
c.booSayHi()
a.reportPrivate()
c.reportPrivate()

### output:
foo says Hi Alice
foo says Hi Bob
foo says Hi undefined
boo says Hi Jane
{ '1': { name: 'Alice' },
  '2': { name: 'Bob' },
  '3': { name: undefined } }
{ '3': { name: 'Jane' } }
###

#-別の実装---------------------------
#こう書いたら
class Test
    constructor: (@name) ->
    greeting: ->
        @internalMethod()

    private:
        internalMethod: ->
            "Hello #{@name}!"

### こう吐き出したらどうか?的な
var Test;
(function() {

    var __private = {};
    Test = function(_a) {
        this.name = _a;
        return this;
    }

    Test.prototype.greeting = function() {
        return __private.internalMethod.call(this);
    }

    //private methods
    __private.internalMethod = function() {
        return ("Hello " + (this.name) + "!");
    }

})();
###


まあ、未だに実装はされていないんだな。
上二つは取りあえずコードを足せば使えるので、
CoffeeScriptで実装されるまでは上記のコードや自前で用意したもので
代用するのが妥当な着地点なのかもしれない。

ああ、protectedも欲しいな。