01 Ruby元编程介绍和使用场景
02 Ruby的类结构
03 Singleton Method单例方法以及super/self的深入讲解
04 Block的进阶使用和面试问题讲解
05 method_missing & define_method
06 class_eval & instance_eval以及深入使用
07 设计模式之Module Mixin
08 append_features和ActiveSupport::Concern的实现
09 Ruby元编程闲话
jayzen补充:
10 class_eval的用法
11 方法和块对象的参数传递以及call使用
12 yield的惯用法
13 define_method和extend结合使用场景
14 inject(reduce)的用法
01 Ruby元编程介绍和使用场景
元编程,类似DSL,一种高度抽象的接口
02 Ruby的类结构
ruby中所有的类都是Class类的实例
A.class #=>Class
Class.class #=>Class
在方法中使用super不仅仅是调用父类的中的方法,实际上是调用其所在类的ancestors类的上一层级的相同方法。
module B
def hi
p 'hi b'
end
end
class A
include B
def hi
super
p 'hi a'
end
end
A.new.hi #=>hi b; hi a;
p A.ancestors #=>[A, B, Object, Kernel, BasicObject]
include extend pretend方法的区别
#include extend的区别
include 模块中的实例方法作为类对象实例方法
extend 模块中的实例方法作为类的类方法
#include pretend
相同点都是讲模块的实例方法引入作为类对象的实例方法
不同点是include方法在ancestors中模块是在类的上一层,而pretend是处于下一层
03 Singleton Method单例方法以及super/self的深入讲解
单例方法的几种形式
# 定义单例方法的方式
# 1
class A
def self.hi
p 'hi'
end
end
# 2
class A
class << self
def hello
p 'hello'
end
end
end
# 3
def A.hey
p 'hey'
end
# 4
(class << A; self; end).class_eval do
def you
p 'you'
end
end
# 5
A.singleton_class.class_eval do
def folk
p 'folk'
end
end
# 6
A.define_singleton_method(:hello) do |*args|
p args
end
self的用法
class A
p self #当前的A类
class << self
p self #A类的单例类
end
def hello
p self #A类的实例化对象
end
end
变量:类的类变量以及类实例变量,实例的实例变量以及类变量
class B
#这两个变量在类B的作用域中
@a = 1
@@b = 2
def initialize
#这两个变量在类B的实例化对象的作用域中
@c = 3
@@d = 4
end
class << self
#这两个变量在单例作用域中
@e = 5
@@f = 6
end
end
b = B.new
p B.instance_variables #=>[:@a]
#说明在B作用域内,只要使用@@符号定义的变量,均为B类的类变量
p B.class_variables #=>[:@@b, :@@f, :@@d]
p b.instance_variables #=>[:@c]
p B.singleton_class.instance_variables #=>[:@e]
#并没有类变量
p B.singleton_class.class_variables #=>[]
变量的一般写法
class B
#类中定义类变量
@@a
def initialize
#实例方法中定义实例变量,同时在实例中可以访问类变量
@b
@@c
end
end
04 Block的进阶使用和面试问题讲解
第一个问题:实现一个计数器功能,每次call这个方法,计数器增加一个数值。
def counter(n)
#代码块会保留当前作用域中的变量
proc { n +=1 }
end
a = counter(10)
a.call #=>11
a.call #=>12
第二题
#测试题, 请实现以下类EnuTest的功能
enu = EnuTest.new do |x|
x << 1
x << 3
x << proc { 'hello' }
end
enu.next # => 1
enu.next # => 3
enu.next # => 'hello'
enu.next # => raise error 'EOF'
# 答案
class EnuTest
def initialize &block
@eb = EnuBlock.new
yield @eb
end
def next
@eb.next
end
end
class EnuBlock
def initialize
@blocks = []
end
def << obj
if obj.is_a? Proc
@blocks << obj
else
@blocks << proc { obj }
end
end
def next
if @blocks.empty?
raise "EOF"
else
@blocks.shift.call
end
end
end
05 method_missing & define_method
#实现class A的代码
class A
end
A.title= "hh"
A.title #=>"hh"
实现方案一:
class A
@@attributes = {}
def self.title=(value)
@@attributes[:title] = value
end
def self.title
@@attributes[:title]
end
end
A.title = "xx"
puts A.title #=> "xx"
实现方案二: 使用method_missing
如果没有从ancestors中寻找到对应的方法,会依次执行从ancestors中的method_missing方法。
class A
@@attributes = {}
class << self
def method_missing method_name, *params
method_name = method_name.to_s
if method_name =~ /=$/
@@attributes[method_name.sub('=', '')] = params.first
elsif @@attributes[method_name]
@@attributes[method_name]
else
"no assignment"
end
end
end
end
实现方案三:define_method
在代码的运行过程当中定义代码
class User < ActiveRecord::Base
STATUS = %w[pending activated suspended]
STATUS.each do |status|
define_method "is_#{status}?" do
self.status == status
end
end
end
上面的代码中define_method定义的方法是在类中进行定义,其方法为实例方法。
如果需要定义类方法,可以在单例类中进行定义,使用class<
06 class_eval & instance_eval以及深入使用
instance_eval定义的是一个单例作用域,定义是方法是单例方法。任何一个类都是Class类的实例对象,因此在普通类上定义,在其中获得的是类方法,在普通对象上定义,获得的是该对象的单例方法。class_eval获得的是一个类作用域,在其中定义方法,获得是类的实例化对象的实例方法,如果要定义类方法,可以在类作用域中使用def self.method的形式定义类方法。
class A
end
A.instance_eval do
def hello
puts "hello"
end
end
puts A.hello
A.class_eval do
def hello
puts "hello"
end
end
puts A.new.hello
#在class_eval中定义类方法
A.class_eval do
def self.hello
puts "hello"
end
end
对应关系
# class_eval => class_exec( module_eval => module_exec)
# instance_eval => instance_exec
class_eval #支持代码块(不支持改变常量值),也支持字符串,可以针对变量进行操作
class_exec #只支持代码块的形式
A.class_eval "@@a = 2"
A.class_eval { @@= 2} #报错
在目标类中触发方法
module B
def self.included base
p 'included...'
base.class_eval do
#在base类中执行下面这个方法
set_logger :hi
end
end
def hi
p 'hi'
end
end
class C
def self.set_logger method_name
# .....
end
include B
end
7 设计模式之 Module Mixin
#最原始的代码
module ActsAsField
def self.included base
base.include InstanceMethods
base.extend ClassMethods
base.class_eval do
@@acts_as_fields = []
end
end
module ClassMethods
def field name, path
#在用到module mixin时候的类变量,必须通过如下的方法进行定义和获取
result = class_variable_get(:@@acts_as_fields)
result << name.to_sym
class_variable_set(:@@acts_as_fields, result)
#define_method在类作用域中进行定义,定定义的方法为实例方法
define_method(name) do
case path
when String
#这里面的self是类Devise的实例对象
path.split(".").inject(self.latest_data) { |data, key| data[key] }
when Proc
path.call(self)
end
end
end
end
module InstanceMethods
def acts_as_fields
self.class.class_variable_get :@@acts_as_fields
end
end
end
class Device
include ActsAsField
field :device_type, "device_type"
field :battery, "data.battery"
field :node_info, "data.node_info"
field :battery_to_text, proc { |device|
"#{device.battery}%"
}
def latest_data
{
"data" => {
"node_info" => "this is a sensor",
"battery" => 90
},
"device_type" => "Sensor"
}
end
end
d = Device.new
p d.node_info
p d.battery_to_text
p d.acts_as_fields
使用activesupport进行改写
require 'active_support/concern'
module ActsAsField
extend ActiveSupport::Concern
included do
@@acts_as_fields = []
end
class_methods do
def field name, path
result = class_variable_get(:@@acts_as_fields)
result << name.to_sym
class_variable_set(:@@acts_as_fields, result)
define_method(name) do
case path
when String
path.split(".").inject(self.latest_data) { |data, key| data[key] }
when Proc
path.call(self)
end
end
end
end
def acts_as_fields
self.class.class_variable_get :@@acts_as_fields
end
end
class Device
include ActsAsField
field :device_type, "device_type"
field :battery, "data.battery"
field :node_info, "data.node_info"
field :battery_to_text, proc { |device|
"#{device.battery}%"
}
def latest_data
{
"data" => {
"node_info" => "this is a sensor",
"battery" => 90
},
"device_type" => "Sensor"
}
end
end
d = Device.new
p d.node_info
p d.battery_to_text
p d.acts_as_fields
9 append_features和ActiveSupport::Concern的实现
module EggConcern
def append_features base
super
base.instance_eval(&@_class_methods)
base.class_eval(&@_class_eval)
end
def included base = nil, &block
super
@_class_eval = block
end
def class_methods &block
@_class_methods = block
end
end
module ActsAsField
extend EggConcern
included do
@@acts_as_fields = []
end
class_methods do
def field name, path
result = class_variable_get(:@@acts_as_fields)
result << name.to_sym
class_variable_set(:@@acts_as_fields, result)
define_method(name) do
case path
when String
path.split(".").inject(self.latest_data) { |data, key| data[key] }
when Proc
path.call(self)
end
end
end
end
def acts_as_fields
self.class.class_variable_get :@@acts_as_fields
end
end
class Device
include ActsAsField
field :device_type, "device_type"
field :battery, "data.battery"
field :node_info, "data.node_info"
field :battery_to_text, proc { |device|
"#{device.battery}%"
}
def latest_data
{
"data" => {
"node_info" => "this is a sensor",
"battery" => 90
},
"device_type" => "Sensor"
}
end
end
d = Device.new
p d.node_info
p d.battery_to_text
p d.acts_as_fields
使用active_support扩展类属性
require 'active_support/all'
#类属性
class A
class_attribute :title
#下面也可以
cattr_accessor :ttile
end
A.title = "dd"
A.title
#模块属性
module B
mattr_accessor :title
end
10 class_eval的用法
参考api中的用法,接受代码块和以及接受String作为参数。
#接受 do..end形式的代码块
class Thing
end
Thing.class_eval do
def demo
puts "this is the demo test"
end
end
Thing.new.demo #=>"this is the demo test"
#接受{}形式的代码块也是一样
class Thing
end
Thing.class_eval {
def demo
puts "this is the demo test"
end
}
Thing.new.demo #=>"this is the demo test"
#接受参数形式的代码块
class Thing
end
a = proc {def demo; puts "this is the demo test"; end}
Thing.class_eval(&a)
Thing.new.demo
#接受参数形式的String对象
class Thing
end
a = %q{def hello; puts "Hello there!"; end}
Thing.class_eval(a)
Thing.new.hello()
11 方法和块对象的参数传递以及call使用
#方法中的参数可以直接传递到块对象中
def demo(n)
proc { puts "this is #{n}"}
end
a = demo(10)
a.call #=> this is 10
#使用call调用参数
def demo
proc {|n| puts "this is #{n}"}
end
a = demo
a.call(10) #=> this is 10
12 yield的惯用法
yield的作用在于方法调用的过程中执行块的内容。
def test &block
yield
end
test do
puts "this is the test"
end
#"this is the test"
介绍yield中使用参数的情况
def test &block
yield "demo" #"demo"作为参数值传递进入下面的块中,并进行返回
end
test do |x|
puts "this is #{x}"
end
#"this is demo"
13 define_method和extend结合使用场景
在类中调用方法,其方法所定义的作于在这个类作用域中,而不是这个类的单例类作用域中,因此这个方法是实例方法,而不是类方法。
module Demo
def self.included(base)
base.include InstanceMethods
base.extend ClassMethods
end
module InstanceMethods
def method_one
puts "this is instance method"
end
end
module ClassMethods
def method_two
define_method(:test) do
puts "this is the instance method"
end
end
end
end
class Test
include Demo
method_two
end
Test.new.test #=>this is the instance method
define_method方法只能被类私有调用,不能被实例化对象调用,因为define_method是Module类的私有实例方法,而Module类是Class类的父类。
module Demo
def self.included(base)
base.include InstanceMethods
base.extend ClassMethods
end
module InstanceMethods
def test
define_method(:test) do
puts "this is instance method"
end
end
end
end
class Test
include Demo
end
Test.new.test #=>undefined method `define_method'
即说明不能在实例中使用define_method方法。
14 inject(reduce)的用法
1.数字求和
[1,2,3,4].inject(0){ |result, element| result+element } #=>10
[1,2,3,4].inject{ |result, element| result+element } #=>10
从上面代码可以得出inject的参数是可选的,代码块是必须的,如果具有参数值,参数值作为首个result的值,被调用对象中的第一个值作为element,第一执行过程result和element的和作为第二次执行中result的值,即是块中的返回值作为下次执行的result值。
如果参数中没有值,则被调用对象的首个数值作为result,下面执行的情况和上面说叙述的类似。
2.创建hash
hash = [[:first_name, 'jay'], [:last_name, 'jayzen']].inject({}) do
|result, element|
result[element.first] = element.last
result
end
创建hash的三种方式:
#第一种方法中,参数必须为偶数形式
Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200}
Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200}
3.创建Array
针对对象中存在两个属性,并且两个属性有关联。
TestResult = Struct.new(:status, :message)
results = [
TestResult.new(:failed, "1 expected but was 2"),
TestResult.new(:sucess),
TestResult.new(:failed, "10 expected but was 20")
]
messages = results.inject([]) do |messages, test_result|
messages << test_result.message if test_result.status == :failed
messages
end
messages # => ["1 expected but was 2", "10 expected but was 20"]
创建Structs的两种方式:
#第一种方式
Struct.new("Customer", :name, :address)#=>Struct::Customer
Struct::Customer.new("Dave", "123 Main")#=> #
#在第一种方式中直接使用Customer.new也是可以的。
#第二种方式
Customer = Struct.new(:name, :address) do
def greeting
"Hello #{name}!"
end
end
Customer.new("Dave", "123 Main").greeting # => "Hello Dave!"
4.创建Hash
相同的状态值设为一组。
TestResult = Struct.new(:status, :message)
results = [
TestResult.new(:failed, "1 expected but was 2"),
TestResult.new(:sucess),
TestResult.new(:failed, "10 expected but was 20")
]
grouped_results = results.inject({}) do |grouped, test_result|
grouped[test_result.status] = [] if grouped[test_result.status].nil?
grouped[test_result.status] << test_result
grouped
end
grouped_results# >> {
:failed => [# >> #,
# >> #],
# >> :sucess => [ # ]# >> }