Recientemente tuve que implementar algo así para rastrear algunas cosas recursivas OO complicadas. Básicamente, quería hacer un método "rastreable" sin contaminarlo demasiado; así que tal vez la solución podría aplicarse aquí también.
En primer lugar, añadir una función que hace que otras funciones trazable:
Function::trace = do ->
makeTracing = (ctorName, fnName, fn) ->
(args...) ->
console.log "#{ctorName}:#{fnName}"
fn.apply @, args
(arg) ->
for own name, fn of arg
@prototype[name] = makeTracing @name, name, fn
Entonces, para usarlo, basta con añadir una @trace
antes de cada método que desea rastrear:
class MyClass
@trace methodA: ->
@methodB 42
@trace methodB: ->
console.log "method b called with #{n}"
O añada @ trace solo una vez y luego sangría todos los métodos trazables un nivel más:
class MyClass
@trace
methodA: ->
@methodB 42
methodB: (n) ->
console.log "method b called with #{n}"
Como puede ver trace
está abusando de la sintaxis de CoffeeScript. method: -> 'foo'
dentro de class MyClass
se interpreta una definición de método. Pero @trace method: -> 'foo'
se interpreta llamando a la función trace
de MyClass
(que es una instancia Function
, a la que hemos agregado la función trace
) pasándole un objeto literal con una clave method
. En JavaScript sería algo así como this.trace({method: function() {return 'foo';}})
.
La función trace
simplemente tomará ese objeto e iterará sus claves (los nombres del método) y los valores (los métodos) y agregará funciones al prototipo MyClass
que registra sus llamadas y llama a los métodos originales.
De todos modos, la salida de (new MyClass).methodA()
será:
MyClass:methodA
MyClass:methodB
method b called with 42
Esta solución no funciona para los constructores, sin embargo, ya que no son sólo los métodos normales.
Puede hacerse bastante elegante con esto. También puede registrar los argumentos pasados a cada método, el valor de retorno e incluso agregar sangrías para llamadas anidadas si así lo desea (los rastros resultantes pueden ser muy útiles si necesita depurar un problema complejo = D).
Actualización: como un ejemplo más interesante, he aquí una mini-versión del ejemplo típico patrón compuesto, figuras y grupos de figuras geométricas: http://jsfiddle.net/2YuE7/ con una función de seguimiento más interesante. Todas las figuras entienden el método move
.Si tenemos esta figura compuesta y llamamos move
en él:
f = new Composite [
new Rectangle 5, 10, 3, 4
new Composite [
new Circle 0, 0, 2
new Circle 0, 0, 4
new Circle 0, 0, 6
]
]
f.move 2, 3
El resultado de seguimiento es:
(Composite[Rectangle[5,10,3,4],Composite[Circle[0,0,2],Circle[0,0,4],Circle[0,0,6]]]).move(2, 3)
| (Rectangle[5,10,3,4]).move(2, 3)
| -> Rectangle[7,13,3,4]
| (Composite[Circle[0,0,2],Circle[0,0,4],Circle[0,0,6]]).move(2, 3)
| | (Circle[0,0,2]).move(2, 3)
| | -> Circle[2,3,2]
| | (Circle[0,0,4]).move(2, 3)
| | -> Circle[2,3,4]
| | (Circle[0,0,6]).move(2, 3)
| | -> Circle[2,3,6]
| -> Composite[Circle[2,3,2],Circle[2,3,4],Circle[2,3,6]]
-> Composite[Rectangle[7,13,3,4],Composite[Circle[2,3,2],Circle[2,3,4],Circle[2,3,6]]]
Esto es muy fresco. La capacidad de registrar argumentos también era algo en lo que estaba pensando. ¡Gracias! –
@FrankLoVecchio Cool. Actualicé la respuesta con un ejemplo que registra parámetros, devuelve valores y sangra los registros dependiendo del nivel de recursión = D – epidemian
Esta va a ser una pregunta de StackOverflow muy útil :) –