Ruby元编程笔记

1.常用方法:

  • Object#class 具体的类信息。[注]#表示实例方法,.表示类方法。
  • Object#class.instance_methods(false) 显示public的实例方法。false表示只显示自己的方法,而不是继承来的。
  • Object#instance_variables 实例变量列表。和Java不同,Ruby中同一个类的不同对象可能有不同的实例变量列表,实例变量存放在对象中。
  • Object#methods 返回所有的公共实例方法。String.instance_methods == "aaa".methods 类的实例表方法表示它的对象的可以访问的方法。
  • Object#singleton_methods 可以得到对象的单件方法或类方法。通过true/false来表示是否显示include的模块的方法。
  • Module.constants 表示当前环境所有的常量,包括定义的类名称。
  • Module#constants 表示当前实例的常量,例如A.constants。由于A是一个Class的变量,Class继承自Module,所以A也就有了constants实例方法。
  • Module#ancestors 可以获取类的父类列表(包括模块),模块会按照声明的顺序正好在声明类的上一层。可以根据这个了解方法调用的路径。 self标识调用方法的对象,在类定义中self表示类自己。
  • Object#send(:method,args…) 可以直接调用对象的方法,这个方法可以调用私有方法,如果为了隐私可以使用public_send
  • Module#define_method方法可以给类定义对象。

2.基本概念:

方法存放在类里,这里的可能是正常的类也可能是eigenclass,每个对象都有一个eigenclass,包括类自身。实例方法存放在类中,单件方法存放在eigenclass中,类方法是存放在类的eigenclass中。

obj是变量,Object、Class、String等是常量,它们也都是Class.new出来的变量。在Ruby中所有对象均是类,也都是对象。只要搞清楚当前执行环境的self、当前类就好理解很多了,self是方法的调用主体,当前类是方法定义的主体。

Object中包含methods等方法的定义,Module中包含instance_methods等方法的定义。由于类如String,MyClass都是Class的实例,所以都可以访问instance_methods,也由于这些类是Class的实例,所以他们可以使用Class的实例方法如new等来定义自己实例对象。上面也说明Object是为实例对象服务的,里面有很多实例方法。而Module、Class是为类服务的,里面有很多类定义相关的方法。

load('load.rb',true)中load.rb中的变量会在作用域之外,我们看不到。但是常量如果不加true会影响现有的作用域,如果仅仅是要执行的结果可以加true,如果还需要常量定义,可以不加或是使用require。

可以通过打开类冲定义方法、新增方法等:

#打开类实例
class String
  def to_alphanumberic
    gsub /[^\w\s]/, ''
  end
end

3.方法的查询路径:

  1. 查询自身(自身的单件类中是否有此方法)
  2. 查询自身所属的类的实例方法
  3. 查询自身所属类的父类的实例方法
  4. 找不到方法则执行method_missing方法

4.method_missing方法:

当某个方法沿着继承链找不到时会调用method_missing方法。核心库delegate动态代理就利用了这个特性,很多适配器库也使用了这个特性。

使用method_missing的问题:

  1. 方法可能导致死循环,使用时应注意。可以采用白名单和super来处理。
  2. 它比平常方法要慢1倍左右。
  3. 当一个幽灵方法和真实方法冲突时,比如继承自Object的方法,真实方法会胜出。这时可以使用白板类,这个类比Object类的方法还要少。ruby1.9之后可以直接继承Basic_Object来变成白板类。
class BlankSlate
  #在一个白板类中隐藏名为给定name的方法
  #但不隐藏instance_eval方法或任何一“__"打头的方法
  def self.hide(name)
    if instance_methods.include?(name.to_s) and
      name  !~ /^(__|instance_eval)/  #|instance_method 可能需要加上
      @hidden_methods ||={}
      @hidden_methods[name.to_sym] = instance_method(name)
      undef_method name
    end
    instance_methods.each { |m| hide(m)}
    #...
end

Module#const_missing()当某个常量找不到时会调用这个方法。如果是在具体类或是Object中定义,则这个类的实例或是所有对象都可以使用此方法。

5.块Block:

在代码中可以通过{}或是do...end关键字来传递块。在代码中可以使用yield(a,b)来调用块。方法中可以通过Kernel#block_given?方法判断当前是否传递了块。

def method2
  (1..10).each do |x|
    yield(x)
    end
end
method2 do |x|
  puts x if x != 4
  break if x == 4
end
#这里的break会对method2中的each生效,打印结果为1,2,3

method2 do |x|
  y ||= 1
  y= y + 1
  puts y
end
#会一直打印2,说明y每次都被初始化。

Ruby中有4中方法可以打包代码以备后用:

  1. 使用块(块不是对象);
  2. 使用Proc。(Proc是块转换的对象)
  3. 使用lambda。lambda会校验参数数目。
  4. 使用方法。

代码 + 绑定 = 块。可以通过binding方法获取当前绑定。eval方法可以指定binding。

块转换为Proc的方法:1.Proc.new; 2.lambda() 3.proc().4.&操作符。然后使用#call()方法执行。

def math(a,b)
  yield(a,b)
end
def teach_math(a,b,&operation)
  puts "Let’s do the math:"
  puts math(a,b, &operation)
end
teach_math(2,3) {|x,y| x+y}

操作符如+-的类型是Proc,可以直接使用call来执行,再加上&可以把Proc转换为块。

6.Ruby的作用域:

Ruby的作用域没有嵌套的概念,当作用域切换时只能看到新的作用域。有3个方式可以重新定义作用域:类定义、模块定义、方法定义。

扁平作用域:Kernel#instance_eval 可以把执行它的对象作为块的self对象,从而块可以访问对象中的所有实例变量、私有方法等。这种块也是扁平作用域,也称为”上下文探针”。#instance_exec功能类似,但是它可以给块传参数。

其实扁平作用域就是利用Class.newclass_evalinstance_eval等在不切换作用域的情况下完成类定义、方法定义、在对象内部执行代码等功能。

使用Module#class_eval在不知道类名的时候打开类,定义方法。它会把当前类切换为当前类,同时设置了self变量。Ruby总是会跟踪当前类,这个和self是不同的,它是self的类。

instance_eval也会修改当前类,它修改的是接收者的eigenclass.

#method1只对a有效。
a.instance_eval do
  def method1; ok;end
end

7.eigenclass:

元类是对象自身的类,可以通过下面的方法获取它。

class BasicObject
  def eigenclass
    class << self; self ; end
  end
end

对象元类的父类是它所属的类。obj.eigenclass.superclass = obj.class

类的元类的父类是类的父类的元类(为了支持类方法继承)。特殊的BasicObject的元类是Class。Object.eigenclass.superclass = BasicObject.eigenclass = Class

单件singleton方法:

str ="hello str"
def str.title?
  self.upcase == self
end

也可以通过下面这种方式定义:

class << an_object
  #这里就是eigenclass的作用域了
end

irb中定义的方法,是作为Object的私有方法存在的。因为irb的self对象是main,是Object的一个实例对象;irb中当前类是Object。

def method1
  puts "method1"
end
"aa".send :method1

8.其他法术

类宏:本质是类中的类方法。

#例子:声明取消的方法:
class Book
  def subtitle
    #...
  end

  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      warn "Warning: #{old_method} is deprecated.Use #{new_method}"
      send(new_method, *args, &block)
    end
  end
  deprecate :title2, :subtitle
end

include模块:在类中调用Module#include方法包含其他模块,模块中定义的方法会成为类的实例方法(通过建立ancestors关系)。如果在类的eigenclass中include模块,模块中定义的方法会成为这个类的类方法,这样其实等效于调用Object#extend方法。

类扩展混入:通过使用钩子方法(ruby中很多钩子方法,包括继承时,包含时调用的)。

Module M
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def my_method
      'a class method'
    end
  end
end

class C
  inlcude M
end

C.my_method #=>"a class method"

空指针保护: a ||= []

参数数组: 使用*号来将多个值转换为数组。

*a = 1, 2, 3 #=> a = [1, 2, 3]

具名参数method(username: "hello", age: 13) 上述值会作为最后一个参数的值,最后一个参数会是hash数组。当默认参数、边长参数、具名参数混用时需要自己进行参数解析。

符号到Proc:把一个符号转换为调用单个方法的代码块:

#这里会自动调用Symbol#to_proc方法
#下面是这个方法的源码
class Symbol
  def to_proc
    Proc.new {|x| x.send(self)}
  end
end
# &符号可以作用于任何对象,会调用它的.to_proc方法.
[1, 2, 3, 4].map(&:even?) #=>[false, true, false, true] 

环绕别名:通过alais关键字定义方法别名,然后把原来方法重定义。

class String
  alias :real_length :length
  
  def length
    real_length > 5 ? 'long' : 'short'
  end
end
"war and peace".length  #=> 'long'
"war and peace".real_length #=> 13

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》