Ruby Tips
include
陷阱
include
定义:
include(module, ...)
→ self click to toggle sourceInvokes Module.append_features on each parameter in reverse order.
append_features(mod) → mod click to toggle source
When this module is included in another, Ruby calls append_features in this module, passing it the receiving module in mod. Ruby’s default implementation is to add the constants, methods, and module variables of this module to mod if this module has not already been added to mod or one of its ancestors. See also Module#include.
include order
举个例子:
module A
end
class Foo
include A
end
p Foo.ancestors
输出:
[Foo, A, Object, Kernel, BasicObject]
对于Foo
,它include
了A
,那么A
在 ancestor chain 上就被 append 到 Foo
的后面。
对于多个include
的情况:
module A
end
module B
end
class Foo
include A
include B
end
class Bar
include A, B
end
p Foo.ancestors
p Bar.ancestors
输出:
[Foo, B, A, Object, Kernel, BasicObject]
[Bar, A, B, Object, Kernel, BasicObject]
这里使用了两种include多个module的语法:
- 多次调用include: 每一次调用都会在 ancestor chain 中的
Foo
后面 append 被 include 的 module - include method 传入多个 module参数:第一个参数被最后include,因此,出现在 ancestor chain 中
Foo
之后的第一个
include scope
举个例子:
module B
end
module A
include B
end
module C
end
module B
include C
end
p A.ancestors
p B.ancestors
输出:
[A, B]
[B, C]
这里之所以 A.ancestors
不等于 [A, B, C]
,是因为,上面的语句等价于:
module A
end
module B
end
module C
end
A.include B
B.include C
在执行 A.include B
之后的作用是将B
append到A
这个Class对象的ancestor chain中。接着,B.include C
改变了B
的 ancestor chain,但是B
的 chain 不会影响 A
的 chain。
这揭示了一点:如果某个类 C
已经include了某个 module M
,之后如果 open M
在其中 include
了另一个 module M2
,那么这不会作用于 C
。只能 open M
在里边定义新的 method/const, 或者 open C
,在里面 include M2
.
method_missing
method_missing
可以用来实现 Ghost Method,即如果某个method没有找到,ruby会调用 method_missing
方法,在这个方法中开发者可以定义各种行为,使用户感觉他调用的方法实际是存在的。
但是,这里需要注意: 所有在 method_missing
中实现的 Ghost Method,实际是不存在的。因此,当用户调用 respond_to?
检查是否存在该method的时候,返回false。这导致了不一致性。解决的方法是,定义respond_to_missing
,在其中判断传入的method是否是已经实现的 Ghost Method,如果是的话返回 true
,否则调用 super
。也就是说,method_missing
和 respond_to_missing
总是配对定义的.
值得一提的是,使用 Ghost Method 的时候,容易遇到一个情况是,你认为某个方法不存在,也即应该由 method_missing
来处理,但是这个方法却被某个父类定义了(例如 Object#display
),这种bug是很难在一开始就预见的。解决的方案是,显示地指定你的父类为 BasicObject
,这个类只定义了以下几个 instance_method
:
pry(main)> BasicObject.instance_methods
=> [:equal?, :!, :__binding__, :==, :instance_exec, :!=, :instance_eval, :__id__, :__send__]
这时候,可以称 BasicObject
是一个 Blank Slate。另外,这时候由于父类中没有定义 respond_to?
方法,因此,我们也不需要定义respond_to_missing
.
最后,慎用 Ghost Method
,只有当所有可以调用的method的集合是未知的情况可以考虑。否则,考虑使用 Dynamic Method
+ Dynamic Dispatch
的方式,显示定义method。
erb debug
Ruby的erb模板文件可以使用 Rubymine 进行调试,但是有一个前提条件,你需要提供erb模板文件的路径,例如:
erb = ERB.new(File.read(filename))
erb.filename = filename
接下来,你就可以在erb中断点调试了 :)
封装已有代码
这里主要讨论如何在不修改已有代码(例如第三方的库中的代码),扩展某些类的某些方法。扩展后的代码有时候仅仅服务于新写的代码,有时候也可能希望这个扩展是全局的,即可以影响到第三方库中的其他调用方。
下面通过一个简单的例子介绍三种扩展方法:
考虑有一个第三方类定义如下:
# FILE: foo.rb
class Foo
def greeting
'Hello'
end
end
Around Aliases
使用 alias
/alias_method
将原方法拷贝到一个alias符号中,然后重新定义该方法(可以在定义中使用alias的原方法):
# FILE: foo_wrapper.rb
require 'foo.rb'
class Foo
# or:
#Foo.class_eval do
alias :old_greeting :greeting
def greeting
"#{old_greeting} magodo"
end
end
任何require了 foo_wrapper.rb 的文件都会有这个新的改动.
Refinement
# FILE: foo_wrapper.rb
require 'foo.rb'
module FooRefinement
refine Foo do
def greeting
"#{old_greeting} magodo"
end
end
end
这种方法适合当你需要严格控制在某几个特定的地方才会使用这个封装之后的方法,其他地方依然使用原来的方法的情况。当需要使用的时候,在开始加上: using FooRefinement
.
Prepend
# FILE: foo_wrapper.rb
require 'foo.rb'
module FooWrapper
def greeting
"#{old_greeting} magodo"
end
end
class Foo
# or:
#Foo.class_eval do
prepend FooWrapper
end
这种方法也是改动在require了文件之后就会生效.
Comments