C.1 法术集
数组参数
把一组参数压入到一个数组中,perl 语言中也有类似用法。
-
def my_method(*args)
-
args.map {|arg| arg.reverse }
-
end
-
my_method('abc' , 'xyz' , '123' ) # => ["cba", "zyx", "321"]
环绕别名
从一个重新定义的方法中调用原始的、被重命名的版本。
三个基本步骤:
-
通过 alias 对原有方法定义一个别名
-
覆写原有方法
-
在该方法中调用别名方法
通过此方式可以改写原来方法,又不破坏原有功能。
-
class String
-
alias :old_reverse :reverse
-
def reverse
-
"x#{old_reverse}x"
-
end
-
end
-
-
"abc".reverse # => "xcbax"
白板
移除一个对象中的所有方法,以便通过 method_missing 添加幽灵方法。主要目的避免原有类中的方法同新增方法产生冲突。注意以 __ 开头的方法不能移除,比如 __send__ 等。
-
class C
-
def method_missing(name, *args)
-
"a Ghost Method"
-
end
-
end
-
-
obj = C.new
-
obj.to_s # => "#"
-
class C
-
instance_methods.each do |m|
-
undef_method m unless m.to_s =~ /method_missing|respond_to?|^__/
-
end
-
end
-
obj.to_s # => "a Ghost Method"
类扩展
通过向 eigenclass 中混入模块来定义类方法(是对象扩展)。
扩展的方法存在于 eigenclass 类中,对类来说就是类方法,对对象实例来说就是单件方法。
提示:一个类,如 class C 具有双重身份。本身是个类,同时又是 Class 类的一个实例。类混入实际上是针对他作为 Class 类的一个实例对象的身份来进行的。
因此类扩展的方式一样适用于对象实例的扩展,那就是对象扩展了。
-
class C; end
-
module M
-
def my_method
-
'a class method'
-
end
-
end
-
-
class << C
-
include M
-
end
-
C.my_method # => "a class method"
类扩展混入
使一个模块可以通过钩子方法扩展它的包含者。
同上面基本类似,差别主要有:
-
通过 extend 方法,避免手工打开 eigenclass (即class << C; end)操作。
-
通过 included 钩子方法触发。
-
可以同时添加实例方法跟类方法(这个例子没有演示)
基本编写方式:
-
定义一个模块,如 MyMixin
-
在 MyMixin 中定义一个内部模块,通常叫 ClassMethods ,并定义一些方法,这些方法会成为包含者的类方法。
-
覆写 MyMixin#included() 方法,extend ClassMethods。
-
module M
-
def self.included(base)
-
base.extend(ClassMethods)
-
end
-
module ClassMethods
-
def my_method
-
'a class method'
-
end
-
end
-
end
-
class C
-
include M
-
end
-
C.my_method # => "a class method"
类实例变量
在一个 Class 对象的实例变量中存储类级别的状态
核心提示:
-
这里的 class C 要当做Class 类的 一个实例对象看待。普通实例对象如何创建实例变量,类实例对象就如何创建实例变量。
-
class ... end 实际上是在运行一段代码,不要用常规的关键字理解。
-
访问类实例变量,只能通过类方法(因为其 self 就是类名),或者加上类名前缀。
想一想如果我们运行时类名动态变化,如何处理,显然我们还有 eval 工具组(使用 instance_eval ,class_eval,eval 均可)
-
class C
-
@my_class_instance_variable = "some value"
-
def self.class_attribute
-
@my_class_instance_variable
-
end
-
end
-
C.class_attribute # => "some value"
类宏
在类定义中使用一个类方法。
就是一个伪装成关键字的类方法。如 attr_accessor :a , :b ;类宏一般结合类扩展混入技术进行。
-
class C; end
-
class << C
-
def my_macro(arg)
-
"my_macro(#{arg}) called"
-
end
-
end
-
class C
-
my_macro :x # => "my_macro(x) called"
-
end
洁净室
使用对象作为执行块的上下文环境
实际上就是通过 instance_eval 限定执行块的作用域。
-
class CleanRoom
-
def a_useful_method(x); x * 2; end
-
end
-
CleanRoom.new.instance_eval { a_useful_method(3) } # => 6
代码处理器
处理从外部获得的字符串代码
-
File.readlines("a_file_containing_lines_of_ruby.txt" ).each do |line|
-
puts "#{line.chomp} ==> #{eval(line)}"
-
end
-
# >> 1 + 1 ==> 2
-
# >> 3 * 2 ==> 6
-
# >> Math.log10(100) ==> 2.0
上下文探针
执行块来获取对象上下文中的信息。
其实就是通过 instance_eval 将对象内部的作用域暴露出来。
-
class C
-
def initialize
-
@x = "a private instance variable"
-
end
-
end
-
obj = C.new
-
obj.instance_eval { @x } # => "a private instance variable"
延迟执行
在 proc 或 lambda中存储一段代码及其上下文,用于以后执行。
-
class C
-
def store(&block)
-
@my_code_capsule = block
-
end
-
def execute
-
@my_code_capsule.call
-
end
-
end
-
-
obj = C.new
-
obj.store { $X = 1 }
-
$X = 0
-
obj.execute
-
$X # => 1
动态派发
在运行时决定调用哪个方法
通过 send 发送消息,等价于方法调用。但通过 send 可以发送符号或字符串,灵活性大为增强。
-
method_to_call = :reverse
-
obj = "abc"
-
obj.send(method_to_call) # => "cba"
动态方法
在运行时才决定如何定义一个方法
动态方法还有一个特性:不会开启一个新的作用域。
我们知道 def 、module、class 会开启新的作用域,扁平化作用域的办法就是用 define_method 、Module.new 、Class.new 等方法调用取代关键字。
-
class C
-
end
-
C.class_eval do
-
define_method :my_method do
-
"a dynamic method"
-
end
-
end
-
obj = C.new
-
obj.my_method # => "a dynamic method"
动态代理
把不能对应某个方法名的消息转发给另外一个对象。
method_missing 结合 send 技术。以下脚本要完善的话,还需要添加 respond_to? 的判断。
-
class MyDynamicProxy
-
def initialize(target)
-
@target = target
-
end
-
def method_missing(name, *args, &block)
-
"result: #{@target.send(name, *args, &block)}"
-
end
-
end
-
obj = MyDynamicProxy.new("a string" )
-
obj.reverse # => "result: gnirts a"
扁平作用域
使用闭包在两个作用域之间共享变量
注:其实这还算不得扁平化作用域,顶多算对象打开,或者作用域切换。或许算半个吧,毕竟 obj 的作用域都被拉到了当前作用域,可以看到 a_variable 变量了。
-
class C
-
def an_attribute
-
@attr
-
end
-
end
-
obj = C.new
-
a_variable = 100
-
# flat scope:
-
obj.instance_eval do
-
@attr = a_variable
-
end
-
obj.an_attribute # => 100
幽灵方法
响应一个没有关联方法的消息
比较简单,直接看代码了
-
class C
-
def method_missing(name, *args)
-
name.to_s.reverse
-
end
-
end
-
obj = C.new
-
obj.my_ghost_method # => "dohtem_tsohg_ym"
钩子方法
通过覆写某个特殊方法来截获对象模型事件。
有点事件驱动的感觉,呵呵。我们能拦截的事件,还是要依赖于 Ruby 提供。希望以后的 Ruby 版本能够想出一套任意添加事件的架构来。
其实现在我们也可以通过环绕别名来拦截一些自己的事件,但总觉得欠那么一点意思。
-
$INHERITORS = []
-
class C
-
def self.inherited(subclass)
-
$INHERITORS << subclass
-
end
-
end
-
class D < C
-
end
-
-
class E < C
-
end
-
-
class F < E
-
end
-
-
$INHERITORS # => [D, E, F]
内核方法
在 Kernel 模块中定义一个方法,使之对所有对象都可用。
puts 估计也是类似的方法,也可能存放在 class Object 中。
-
module Kernel
-
def a_method
-
"a kernel method"
-
end
-
end
-
a_method # => "a kernel method"
惰性实例变量
当第一次访问一个实例变量时才对之进行初始化。
这个称为法术,有点勉为其难。
-
class C
-
def attribute
-
@attribute = @attribute || "some value"
-
end
-
end
-
obj = C.new
-
obj.attribute # => "some value"
拟态方法
把一个方法伪装成另外一种语言构件。
用来编写 DSL 比较方便。
-
def BaseClass(name)
-
name == "string" ? String : Object
-
end
-
class C < BaseClass "string" # a method that looks like a class
-
attr_accessor :an_attribute # 伪装成关键字的方法
-
end
-
obj = C.new
-
obj.an_attribute = 1 # 伪装成属性的方法
猴子打补丁
修改已有类的特性
一般还是要尽量避免,安全第一。
-
"abc".reverse # => "cba"
-
class String
-
def reverse
-
"override"
-
end
-
end
-
"abc".reverse # => "override"
有名参数
把方法参数收集到一个哈希表中,以便通过名字访问。
咦,跟第一条法术差不多。
-
def my_method(args)
-
args[:arg2]
-
end
-
my_method(:arg1 => "A" , :arg2 => "B" , :arg3 => "C" ) # => "B"
命名空间
在一个模块中定义常量,以防止命名冲突。
其实就是作用域门的灵活运用。
-
module MyNamespace
-
class Array
-
def to_s
-
"my class"
-
end
-
end
-
end
-
Array.new # => []
-
MyNamespace::Array.new # => my class
空指针保护
用“或”操作符覆写一个空应用。就是 perl 的短路操作符
-
x = nil
-
y = x || "a value" # => "a value"
对象扩展
通过给一个对象 eigenclass 混入模块来定义单件方法。
咦,前面提到过啊。那是类扩展,对象扩展其实是一回事。你把一个类当成 Class 的实例对象就全明白了。
-
obj = Object.new
-
module M
-
def my_method
-
'a singleton method'
-
end
-
end
-
class << obj
-
include M
-
end
-
obj.my_method # => "a singleton method"
打开类
修改已有的类
这不就是猴子补丁吗?
-
class String
-
def my_string_method
-
"my method"
-
end
-
end
-
"abc".my_string_method # => "my method"
模式派发
根据名字来选择需要调用的方法。
跟动态派发一回事,何必重复呢。
-
$x = 0
-
class C
-
def my_first_method
-
$x += 1
-
end
-
def my_second_method
-
$x += 2
-
end
-
end
-
obj = C.new
-
obj.methods.each do |m|
-
obj.send(m) if m.to_s =~ /^my_/
-
end
-
-
$x # => 3
沙盒
在一个安全的环境中执行为授信的代码
-
def sandbox(&code)
-
proc {
-
$SAFE = 2
-
yield
-
}.call
-
end
-
-
begin
-
sandbox { File.delete 'a_file' }
-
rescue Exception => ex
-
ex # => #<SecurityError: Insecure operation `delete
作用域门
用 class、module 或 def 关键字来隔离作用域
这个是核心概念,要掌握好。再次提问,如何扁平化作用域(答案见前面)?
-
a = 1
-
defined? a # => "local-variable"
-
-
module MyModule
-
b = 1
-
defined? a # => nil
-
defined? b # => "local-variable"
-
end
-
-
defined? a # => "local-variable"
-
defined? b # => nil
Self Yield
把 self 传给当前块
非常有趣的特性,可以大幅简化编码,这应该是从函数式编程学来的吧。
-
class Person
-
attr_accessor :name, :surname
-
def initialize
-
yield self
-
end
-
end
-
-
joe = Person.new do |p|
-
p.name = 'Joe'
-
p.surname = 'Smith'
-
end
共享作用域
在同一个扁平作用域的多个上下文中共享变量
利用闭包,将共享变量保护起来。像不像一个类,还是有区别的,作用域不同。
-
lambda {
-
shared = 10
-
self.class.class_eval do
-
define_method :counter do
-
shared
-
end
-
define_method :down do
-
shared -= 1
-
end
-
end
-
}.call
-
-
counter # => 10
-
3.times { down }
-
counter # => 7
单件方法
在一个对象上定义一个方法,其实是在 该对象 eigenclass 中定义了一个实例方法。 如果 obj 换成类,就是类方法(其实也是实例方法,不过应该叫 Class 的这个特定对象的实例方法)
-
obj = "abc"
-
class << obj
-
def my_singleton_method
-
"x"
-
end
-
end
-
obj.my_singleton_method # => "x"
代码字符串
执行一段表示 Ruby 代码的字符串。
动态语言没这个功能,都不好意思出来见人。
-
my_string_of_code = "1 + 1"
-
eval(my_string_of_code) # => 2
符号到 Proc
把一个符号转换为调用单个方法的代码块。
充分体现 Ruby 变态文化的经典例子。(如果没有 Ruby 这么宽松,优秀的平台,能这么变态吗?)。
-
[1, 2, 3, 4].map(&:even?) # => [false, true, false, true]
阅读(3274) | 评论(0) | 转发(0) |