基盤となるJavascriptエンジンにHarmonyプロキシが存在すると仮定すると、CoffeeScriptスーパークラスを拡張して、クラスがnoSuchMethodメソッド(またはmethodMessing)を定義できるようにするにはどうすればよいでしょうか。
クラス(またはそのスーパークラス)に要求されたメソッドがない場合、そのメソッドは名前と引数リストを使用して呼び出されます。
基盤となるJavascriptエンジンにHarmonyプロキシが存在すると仮定すると、CoffeeScriptスーパークラスを拡張して、クラスがnoSuchMethodメソッド(またはmethodMessing)を定義できるようにするにはどうすればよいでしょうか。
クラス(またはそのスーパークラス)に要求されたメソッドがない場合、そのメソッドは名前と引数リストを使用して呼び出されます。
いい質問です!= D
(注:これはFirefoxでのみテストしました。これは、Harmonyプロキシをサポートする唯一のブラウザーのようです。)
これは、不足しているプロパティに対して機能するようです:
class DynamicObject
propertyMissingHandler =
get: (target, name) ->
if name of target
target[name]
else
target.propertyMissing name
constructor: ->
return new Proxy @, propertyMissingHandler
# By default return undefined like a normal JS object.
propertyMissing: -> undefined
class Repeater extends DynamicObject
exited: no
propertyMissing: (name) ->
if @exited then "#{name.toUpperCase()}!" else name
r = new Repeater
console.log r.hi # -> hi
console.log r.exited # -> false. Doesn't print "exited" ;)
r.exited = yes
console.log r.omg # -> OMG!
現在は機能しますが、小さな大きな注意点があります。「他の型の」コンストラクターに依存しています。つまり、DynamicObjectのコンストラクターは、DynamicObjectインスタンス以外のものを返します(インスタンスをラップするプロキシを返します)。他の型付きコンストラクターには微妙な問題とそれほど微妙ではない問題があり、CoffeeScriptコミュニティではあまり愛されていない機能です。
たとえば、上記は(CoffeeScript 1.4で)機能しますが、Repeater用に生成されたコンストラクターが、スーパーコンストラクターを呼び出した結果を返す(したがって、プロキシオブジェクトを返す)ためです。Repeaterに別のコンストラクターがある場合、それは機能しません。
class Repeater extends DynamicObject
# Innocent looking constructor.
constructor: (exited = no) ->
@exited = exited
propertyMissing: (name) ->
if @exited then "#{name.toUpperCase()}!" else name
console.log (new Repeater yes).hello # -> undefined :(
スーパーコンストラクターを呼び出した結果を明示的に返す必要があります。
constructor: (exited = no) ->
@exited = exited
return super
したがって、他の型付きコンストラクターはやや混乱/壊れているので、それらを避け、クラスメソッドを使用してこれらのオブジェクトをインスタンス化することをお勧めしますnew
:
class DynamicObject
propertyMissingHandler =
get: (target, name) ->
if name of target
target[name]
else
target.propertyMissing name
# Use create instead of 'new'.
@create = (args...) ->
instance = new @ args...
new Proxy instance, propertyMissingHandler
# By default return undefined like a normal JS object.
propertyMissing: -> undefined
class Repeater extends DynamicObject
constructor: (exited = no) ->
@exited = exited
# No need to worry about 'return'
propertyMissing: (name) ->
if @exited then "#{name.toUpperCase()}!" else name
console.log (Repeater.create yes).hello # -> HELLO!
さて、不足しているメソッドの場合、質問で要求されたものと同じインターフェイスを持つために、プロキシハンドラーで同様のことを行うことができますが、ターゲットにプロパティがない場合にターゲットで特別なメソッド(propertyMissing)を直接呼び出す代わりにname、それは関数を返し、それは次に特別なメソッド(methodMissing)を呼び出します:
class DynamicObject2
methodMissingHandler =
get: (target, name) ->
return target[name] if name of target
(args...) ->
target.methodMissing name, args
# Use this instead of 'new'.
@create = (args...) ->
instance = new @ args...
new Proxy instance, methodMissingHandler
# By default behave somewhat similar to normal missing method calls.
methodMissing: (name) -> throw new TypeError "#{name} is not a function"
class CommandLine extends DynamicObject2
cd: (path) ->
# Usually 'cd' is not a program on its own.
console.log "Changing path to #{path}" # TODO implement me
methodMissing: (name, args) ->
command = "#{name} #{args.join ' '}"
console.log "Executing command '#{command}'"
cl = CommandLine.create()
cl.cd '/home/bob/coffee-example' # -> Changing path to /home/bob/coffee-example
cl.coffee '-wc', 'example.coffee' # -> Executing command 'coffee -wc example.coffee'
cl.rm '-rf', '*.js' # -> Executing command 'rm -rf *.js'
残念ながら、プロパティアクセスをプロキシハンドラーのメソッド呼び出しと区別する方法を見つけることができなかったため、DynamicObjectはよりインテリジェントになり、それに応じてpropertyMissingまたはmethodMissingを呼び出すことができます(ただし、メソッド呼び出しは単にプロパティアクセスとそれに続くプロパティアクセスであるため、理にかなっています)関数呼び出し)。
DynamicObjectを選択して可能な限り柔軟にする必要がある場合は、propertyMissingの実装を使用します。これは、サブクラスがpropertyMissingの実装方法を選択し、欠落しているプロパティをメソッドとして処理するかどうかを選択できるためです。propertyMissingに関して実装された上記のCommandLineの例は次のようになります。
class CommandLine extends DynamicObject
cd: (path) ->
# Usually 'cd' is not a program on its own.
console.log "Changing path to #{path}" # TODO implement me
propertyMissing: (name) ->
(args...) ->
command = "#{name} #{args.join ' '}"
console.log "Executing command '#{command}'"
これで、同じ基本クラスから継承するリピーターとコマンドラインを混在させることができます(どれほど便利です!= P):
cl = CommandLine.create()
r = Repeater.create yes
cl.echo r['hello proxies'] # -> Executing command 'echo HELLO PROXIES!'