Ruby元编程技巧
(摘自ruby元编程原书附录)
常见惯用法
A.1 拟态方法(Mimic Methods)
puts "Hello,world!"
这里的 puts 实际上是个方法,完整写法
puts("Hello,world!")
去掉括号的写法使得它像个关键字,同时也更为简洁,因此称之为拟态方法。
属性的问题:
class C
def my_attr=(value)
@p = value
end
def my_attr
@p
end
end
obj = C.new
obj.my_attr = 'some value'
obj.my_attr # => 'some value'
代码obj.my_attr = ‘some value’的功能与代码obj.my_attr=(‘some value’)的功能是相同的,不过前者看起来更清爽。
来自 Camping 的例子,这里的 R 实际上是一个方法,’/help’ 是它的参数,返回值是一个 Class的实例。
class Help < R '/help'
def get
# rendering for HTTP GET...
end
end
A.2 空指针保护(Nil Guards)
a ||= []
以上代码等价于
a = a || []
空指针保护常用于初始化实例变量,看看下面这个类:
class C
def initialize(value)
@a = []
end
def elements
@a
end
end
使用空指针保护,可以更简练地重写以上这段代码:
class C
def elements
@a ||= []
end
end
上面的代码会在最后它要被访问的时候才进行初始化,这种惯用法被称为惰性实例变量(Lazy Instance Variable)。
A.3 关于方法参数的技巧(Tricks with Method Arguments)
####具名参数(Named Arguments)
当在Ruby中调用方法时,你不得不按照特定的顺序输入参数。如果顺序错误,则会引入一个bug:
def login(name, password, message)
#...
end
login('james', 'just doing some administration', '123456') #bug
当有一大串参数要输入时,这种错误非常常见。 Ruby 2.0 以后,可以使用关键字参数,Ruby 2.0可以使用Hash参数来解决这个问题。
login(name: 'bill', message: 'just doing some administration', password: '123456')
参数数组和默认值(Argument Arrays and Default Values)
*操作符可以把多个参数收集到一个数组中;
def my_method(*args)
args
end
my_method(1, '2' , 'three' ) # =]]> [1, "2", "three"]
Ruby 也支持如下参数默认值:
def my_method(x, y = "a default value" )
"#{x} and #{y}"
end
my_method("a value" ) # => "a value and a default value"
混合使用参数的惯用法(Mixing Arguments Idioms)
Ruby 2.0以前
def my_method(arg, hash)
lots = hash[:lots] || "default"
args = hash[:args] || "another"
hand = hash[:by_hand] || "annoying"
...
end
my_method "with", lots: "of", args: "in", by_hand: "form"
Ruby 2.0以后
def my_method(arg, lots: "default", args: "another", by_hand: "annoying")
lots = hash[:lots] || "default"
args = hash[:args] || "another"
hand = hash[:by_hand] || "annoying"
...
end
my_method "with", lots: "of", args: "in", by_hand: "form"
A.4 Self Yield
当给方法传入一个块时,你会期望这个方法通过yield对块进行回调。这种回调有一种有用的变形,就是对象可以把自身传给这个块。下面的例子来自于RubyGems包管理器。
传统写法
spec = Gem::Specification.new
spec.name = "My Gem name"
spec.version = "0.0.1"
# ...
Self Yield 写法
spec = Gem::Specification.new do |s|
s.name = "My Gem name"
s.version = "0.0.1"
# ...
end
Gem::Specification 源代码
module Gem
class Specification
def initialize
yield self if block_given?
# ...
end
#...
end
end
来自 tap() 的例子
在Ruby中,长长的方法调用链很普遍
['a', 'b', 'c'].push('d' ).shift.upcase.next # => "B"
但是某一步出错,你将不得不如下调试
temp = ['a' , 'b' , 'c' ].push('d' ).shift
puts temp
x = temp.upcase.next
这非常笨拙;Ruby 1.9 中引入了 tap() 方法,我们可以这样做
['a' , 'b' , 'c' ].push('d' ).shift.tap {|x| puts x }.upcase.next
老版本的 Ruby ,我们也可以很容易的实现一个
class Object
def tap
yield self
self
end
end
A.5 Symbol#to_proc() 方法
这种有点诡异的法术在Ruby程序员中很流行,请看下面的代码
names = ['bob' , 'bill' , 'heather' ]
names.map {|name| name.capitalize } # => ["Bob", "Bill", "Heather"]
更简洁的写法
names = ['bob' , 'bill' , 'heather' ]
names.map(&:capitalize) # => ["Bob", "Bill", "Heather"]
当&操作符作用于一个对象时,它会调用该对象的to_proc方法,将其转化为一个proc对象。
class Symbol
def to_proc
Proc.new {|x| x.send(self) }
end
end
法术手册
法术集 (The Spells)
数组参数(Argument Array)
把一组参数压入到一个数组中。
def my_method(*args)
args.map {|arg| arg.reverse }
end
my_method('abc' , 'xyz' , '123' ) # =]]> ["cba", "zyx", "321"]
环绕别名(Around Alias)
从一个重新定义的方法中调用原始的、被重命名的版本。 三个基本步骤: 1. 通过 alias 对原有方法定义一个别名 2. 覆写原有方法 3. 在该方法中调用别名方法 通过此方式可以改写原来方法,又不破坏原有功能。
class String
alias :old_reverse :reverse
def reverse
"x#{old_reverse}x"
end
end
"abc".reverse # => "xcbax"
白板(Blank Slate)
移除一个对象中的所有方法,以便通过method_missing添加幽灵方法。主要目的避免原有类中的方法同新增方法产生冲突。注意以__开头的方法不能移除,比如__send__等。
class C
def method_missing(name, *args)
"a Ghost Method"
end
end
obj = C.new
obj.to_s # => "#<C:0x357258>"
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"
类扩展(Class Extension)
通过向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"
类扩展混入(Class Extension Mixin)
使一个模块可以通过钩子方法扩展它的包含者。 同上面基本类似,差别主要有:
- 通过 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 Instance Variable)
在一个 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"
类宏(Class Macro)
在类定义中使用一个类方法。 就是一个伪装成关键字的类方法。如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
洁净室(Clean Room)
使用对象作为执行块的上下文环境 实际上就是通过 instance_eval 限定执行块的作用域。
class CleanRoom
def a_useful_method(x); x * 2; end
end
CleanRoom.new.instance_eval { a_useful_method(3) } # => 6
代码处理器(Code Processor)
处理从外部获得的字符串代码
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
上下文探针(Context Probe)
执行块来获取对象上下文中的信息。 其实就是通过 instance_eval 将对象内部的作用域暴露出来。
class C
def initialize
@x = "a private instance variable"
end
end
obj = C.new
obj.instance_eval { @x } # => "a private instance variable"
延迟执行(Deferred Evaluation)
在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
动态派发(Dynamic Dispatcher)
在运行时决定调用哪个方法 通过send发送消息,等价于方法调用。但通过send可以发送符号或字符串,灵活性大为增强。
method_to_call = :reverse
obj = "abc"
obj.send(method_to_call) # => "cba"
动态方法(Dynamic Method)
在运行时才决定如何定义一个方法 动态方法还有一个特性:不会开启一个新的作用域。 我们知道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"
动态代理(Dynamic Proxy)
把不能对应某个方法名的消息转发给另外一个对象。 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"
扁平作用域(Flat Scope)
使用闭包在两个作用域之间共享变量(以下用例不典型)
class C
def an_attribute
@attr
end
end
obj = C.new
a_variable = 100
# flatscope:
obj.instance_eval do
@attr = a_variable
end
obj.an_attribute # => 100
幽灵方法(Ghost Method)
响应一个没有关联方法的消息
class C
def method_missing(name, *args)
name.to_s.reverse
end
end
obj = C.new
obj.my_ghost_method # => "dohtem_tsohg_ym"
钩子方法(Hook Method)
通过覆写某个特殊方法来截获对象模型事件。
$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]
内核方法(Kernal Method)
在 Kernel 模块中定义一个方法,使之对所有对象都可用。
module Kernel
def a_method
"a kernel method"
end
end
a_method # => "a kernel method"
惰性实例变量(Lazy Instance Variable)
当第一次访问一个实例变量时才对之进行初始化。
class C
def attribute
@attribute ||= "some value"
end
end
obj = C.new
obj.attribute # => "some value"
拟态方法(Mimic Method)
把一个方法伪装成另外一种语言构件。
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 # 伪装成属性的方法
猴子打补丁(Monkeypatch)
修改已有类的特性。
"abc".reverse # => "cba"
class String
def reverse
"override"
end
end
"abc".reverse # => "override"
有名参数(Named Arguments)
把方法参数收集到一个哈希表中,以便通过名字访问(Ruby2.0+直接支持)。
def my_method(args)
args[:arg2]
end
my_method(:arg1 => "A" , :arg2 => "B" , :arg3 => "C") # => "B"
命名空间(Namespace)
在一个模块中定义常量,以防止命名冲突。
module MyNamespace
class Array
def to_s
"my class"
end
end
end
Array.new # => []
MyNamespace::Array.new # => my class
空指针保护(Nil Guard)
用“或”操作符覆写一个空应用。
x = nil
y = x || "a value" # => "a value"
对象扩展(Object Extension)
通过给一个对象 eigenclass 混入模块来定义单件方法。
obj = Object.new
module M
def my_method
'a singleton method'
end
end
class << obj
include M
end
obj.my_method # => "a singleton method"
打开类(Open Class)
修改已有的类
class String
def my_string_method
"my method"
end
end
"abc".my_string_method # => "my method"
模式派发(Pattern Dispatch)
根据名字来选择需要调用的方法。
$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
沙盒(Sandbox)
在一个安全的环境中执行为授信的代码
def sandbox(&code)
proc {
$SAFE = 2
yield
}.call
end
begin
sandbox { File.delete 'a_file' }
rescue Exception => ex
ex # => #<SecurityError: Insecure operation `delete' at level 2>
end
作用域门(Scope Gate)
用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
共享作用域(Shared Scope)
在同一个扁平作用域的多个上下文中共享变量。
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
单件方法(Singleton Method)
在一个对象上定义一个方法,其实是在该对象eigenclass中定义了一个实例方法。
obj = "abc"
class << obj
def my_singleton_method
"x"
end
end
obj.my_singleton_method # => "x"
代码字符串(String of Code)
执行一段表示 Ruby 代码的字符串。
my_string_of_code = "1 + 1"
eval(my_string_of_code) # => 2
符号到Proc(Symbol To Proc)
把一个符号转换为调用单个方法的代码块。
[1, 2, 3, 4].map(&:even?) # => [false, true, false, true]