どうも、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も欲しいな。