我今天花在从Python角度学习Ruby.我完全没能解决的一件事是相当于装饰者.为了削减一些东西,我试图复制一个简单的Python装饰器:
#! /usr/bin/env python
import math
def document(f):
def wrap(x):
print "I am going to square", x
f(x)
return wrap
@document
def square(x):
print math.pow(x, 2)
square(5)
运行这个给了我:
I am going to square 5 25.0
所以,我想创建一个函数square(x),但是要对它进行修饰,以便在它发生之前提醒我它将会变成什么样.让我们摆脱糖,使其更基本:
...
def square(x):
print math.pow(x, 2)
square = document(square)
...
那么,我如何在Ruby中复制它?这是我的第一次尝试:
#! /usr/bin/env ruby
def document(f)
def wrap(x)
puts "I am going to square", x
f(x)
end
return wrap
end
def square(x)
puts x**2
end
square = document(square)
square(5)
运行此生成:
./ruby_decorate.rb:8:in `document': wrong number of arguments (0 for 1) (ArgumentError)
from ./ruby_decorate.rb:15:in `'
我猜它是因为括号不是强制性的,它将我的"返回换行"作为"返回换行()"的尝试.我知道没有调用它就无法引用函数.
我尝试了其他各种各样的东西,但没有什么能让我走得更远.
hor*_*guy 13
这是另一种方法,消除了别名方法名称之间冲突的问题(注意我使用模块进行装饰的其他解决方案也是一个很好的选择,因为它也避免了冲突):
module Documenter
def document(func_name)
old_method = instance_method(func_name)
define_method(func_name) do |*args|
puts "about to call #{func_name}(#{args.join(', ')})"
old_method.bind(self).call(*args)
end
end
end
Run Code Online (Sandbox Code Playgroud)
上面的代码是有效的,因为old_method局部变量在新的'hello'方法中保持活动,因为define_methodblock是一个闭包.
jam*_*her 11
好的,我尝试回答的时间到了.我特意瞄准Pythoneers试图重新组织他们的大脑.这里有一些记录严密的代码(大约)执行我最初尝试做的事情:
#! /usr/bin/env ruby
# First, understand that decoration is not 'built in'. You have to make
# your class aware of the concept of decoration. Let's make a module for this.
module Documenter
def document(func_name) # This is the function that will DO the decoration: given a function, it'll extend it to have 'documentation' functionality.
new_name_for_old_function = "#{func_name}_old".to_sym # We extend the old function by 'replacing' it - but to do that, we need to preserve the old one so we can still call it from the snazzy new function.
alias_method(new_name_for_old_function, func_name) # This function, alias_method(), does what it says on the tin - allows us to call either function name to do the same thing. So now we have TWO references to the OLD crappy function. Note that alias_method is NOT a built-in function, but is a method of Class - that's one reason we're doing this from a module.
define_method(func_name) do |*args| # Here we're writing a new method with the name func_name. Yes, that means we're REPLACING the old method.
puts "about to call #{func_name}(#{args.join(', ')})" # ... do whatever extended functionality you want here ...
send(new_name_for_old_function, *args) # This is the same as `self.send`. `self` here is an instance of your extended class. As we had TWO references to the original method, we still have one left over, so we can call it here.
end
end
end
class Squarer # Drop any idea of doing things outside of classes. Your method to decorate has to be in a class/instance rather than floating globally, because the afore-used functions alias_method and define_method are not global.
extend Documenter # We have to give our class the ability to document its functions. Note we EXTEND, not INCLUDE - this gives Squarer, which is an INSTANCE of Class, the class method document() - we would use `include` if we wanted to give INSTANCES of Squarer the method `document`. <http://blog.jayfields.com/2006/05/ruby-extend-and-include.html>
def square(x) # Define our crappy undocumented function.
puts x**2
end
document(:square) # this is the same as `self.document`. `self` here is the CLASS. Because we EXTENDED it, we have access to `document` from the class rather than an instance. `square()` is now jazzed up for every instance of Squarer.
def cube(x) # Yes, the Squarer class has got a bit to big for its boots
puts x**3
end
document(:cube)
end
# Now you can play with squarers all day long, blissfully unaware of its ability to `document` itself.
squarer = Squarer.new
squarer.square(5)
squarer.cube(5)
Run Code Online (Sandbox Code Playgroud)
仍然困惑?我不会感到惊讶; 这几乎花了整整一天.你应该知道的其他一些事情:
上面的代码修饰了实例方法.如果你想直接在类上装饰方法怎么办?如果您阅读http://www.rubyfleebie.com/understanding-class-methods-in-ruby,您会发现创建类方法有三种方法 - 但这里只有一种方法适合我们.
这是匿名class << self技术.让我们做上面的事情,但所以我们可以调用square()和cube()而不实例化它:
class Squarer
class << self # class methods go in here
extend Documenter
def square(x)
puts x**2
end
document(:square)
def cube(x)
puts x**3
end
document(:cube)
end
end
Squarer.square(5)
Squarer.cube(5)
Run Code Online (Sandbox Code Playgroud)
玩得开心!
类似于Python的装饰器可以在Ruby中实现.我不会尝试解释和举例,因为Yehuda Katz已经发布了一篇关于Ruby中装饰器DSL的好博文,所以我强烈推荐阅读它:
更新:我在这个问题上有几个投票,所以让我进一步解释.
alias_method (and alias_method_chain)与装饰者的概念不完全相同.它只是一种在不使用继承的情况下重新定义方法实现的方法(因此客户端代码不会注意到差异,仍然使用相同的方法调用).它可能很有用.但它也可能容易出错.任何使用Ruby的Gettext库的人可能都注意到它的ActiveRecord集成已经被每个Rails主要升级打破了,因为别名版本一直遵循旧方法的语义.
一般来说,装饰器的目的不是改变任何给定方法的内部结构,而是仍然能够从修改后的版本调用原始方法,而是增强函数行为."进入/退出"用例有点接近alias_method_chain,只是一个简单的演示.另一个更有用的装饰器类型可以@login_required检查授权,只在授权成功时运行该函数,或者@trace(arg1, arg2, arg3)可以执行一组跟踪过程(并且可以使用不同的参数调用不同的方法装饰).
| 归档时间: |
|
| 查看次数: |
8534 次 |
| 最近记录: |