Ruby中钩子方法的运用实例解析

所属分类: 脚本专栏 / ruby专题 阅读数: 1969
收藏 0 赞 0 分享

通过使用钩子方法,可以让我们在Ruby的类或模块的生命周期中进行干预,可以极大的提高编程的灵活性。
与生命周期相关的钩子方法有下面这些:

类与模块相关

  • Class#inherited
  • Module#include
  • Module#prepended
  • Module#extend_object
  • Module#method_added
  • Module#method_removed
  • Module#method_undefined

单件类相关

  • BasicObject#singleton_method_added
  • BasicObject#singleton_method_removed
  • BasicObject#singleton_method_undefined

示例代码

module M1
  def self.included(othermod)
    puts “M1 was included into #{othermod}”
  end
end

module M2
  def self.prepended(othermod)
    puts “M2 was prepended to #{othermod}”
  end
end

class C
  include M1
  include M2
end

# 输出
M1 was included into C
M2 was prepended to C

module M
  def self.method_added(method)
    puts “New method: M##{method}”
  end

  def my_method; end
end

# 输出
New method: M#my_method

除了上面列出来的一些方法外,也可以通过重写父类的某个方法,进行一些过滤操作后,再通过调用super方法完成原函数的功能,从而实现类似钩子方法的功效,如出一辙,环绕别名也可以作为一种钩子方法的替代实现。

运用实例
任务描述:

写一个操作方法类似attr_accessor的attr_checked的类宏,该类宏用来对属性值做检验,使用方法如下:

class Person
 include CheckedAttributes

 attr_checked :age do |v|
  v >= 18
 end
end

me = Person.new
me.age = 39 #ok
me.age = 12 #抛出异常

实施计划:

使用eval方法编写一个名为add_checked_attribute的内核方法,为指定类添加经过简单校验的属性
重构add_checked_attribute方法,去掉eval方法,改用其它手段实现
添加代码块校验功能
修改add_checked_attribute为要求的attr_checked,并使其对所有类都可用
通过引入模块的方式,只对引入该功能模块的类添加attr_checked方法
Step 1

def add_checked_attribute(klass, attribute)
 eval "
  class #{klass}
   def #{attribute}=(value)
    raise 'Invalid attribute' unless value
    @#{attribute} = value
   end
   def #{attribute}()
    @#{attribute}
   end
  end
 "
end

add_checked_attribute(String, :my_attr)
t = "hello,kitty"

t.my_attr = 100
puts t.my_attr

t.my_attr = false
puts t.my_attr

这一步使用eval方法,用class和def关键词分别打开类,且定义了指定的属性的get和set方法,其中的set方法会简单的判断值是否为空(nil 或 false),如果是则抛出Invalid attribute异常。

Setp 2

def add_checked_attribute(klass, attribute)
 klass.class_eval do
  define_method "#{attribute}=" do |value|
   raise "Invaild attribute" unless value
   instance_variable_set("@#{attribute}", value)
  end

  define_method attribute do
   instance_variable_get "@#{attribute}"
  end

 end
end

这一步更换掉了eval方法,同时也分别用class_eval和define_method方法替换了之前的class与def关键字,实例变量的设置和获取分别改用了instance_variable_set和instance_variable_get方法,使用上与第一步没有任何区别,只是一些内部实现的差异。

Step 3

def add_checked_attribute(klass, attribute, &validation)
 klass.class_eval do
  define_method "#{attribute}=" do |value|
   raise "Invaild attribute" unless validation.call(value)
   instance_variable_set("@#{attribute}", value)
  end

  define_method attribute do
   instance_variable_get "@#{attribute}"
  end

 end
end

add_checked_attribute(String, :my_attr){|v| v >= 180 }
t = "hello,kitty"

t.my_attr = 100 #Invaild attribute (RuntimeError)
puts t.my_attr

t.my_attr = 200
puts t.my_attr #200

没有什么奇特的,只是加了通过代码块验证,增加了校验的灵活性,不再仅仅局限于nil和false之间了。

Step 4

class Class
 def attr_checked(attribute, &validation)
   define_method "#{attribute}=" do |value|
    raise "Invaild attribute" unless validation.call(value)
    instance_variable_set("@#{attribute}", value)
   end

   define_method attribute do
    instance_variable_get "@#{attribute}"
   end
 end
end

String.add_checked(:my_attr){|v| v >= 180 }
t = "hello,kitty"

t.my_attr = 100 #Invaild attribute (RuntimeError)
puts t.my_attr

t.my_attr = 200
puts t.my_attr #200

这里我们把之前顶级作用域中方法名放到了Class中,由于所有对象都是Class的实例, 所以这里定义的实例方法,也能被Ruby中的其它所有类访问到,同时在class定义中,self就是当前类,所以也就省去了调用类这个参数和class_eval方法,并且我们把方法的名字也改成了attr_checked。

Step 5

module CheckedAttributes
 def self.included(base)
  base.extend ClassMethods
 end
end

module ClassMethods
 def attr_checked(attribute, &validation)
   define_method "#{attribute}=" do |value|
    raise "Invaild attribute" unless validation.call(value)
    instance_variable_set("@#{attribute}", value)
   end

   define_method attribute do
    instance_variable_get "@#{attribute}"
   end
 end
end

class Person
 include CheckedAttributes

 attr_checked :age do |v|
  v >= 18
 end
end

最后一步通过钩子方法,在CheckedAttributes模块被引入后,对当前类通过被引入模块进行扩展, 从而使当前类支持引入后的方法调用,即这里的get与set方法组。

到此,我们已经得到了一个名为attr_checked,类似attr_accessor的类宏,通过它你可以对属性进行你想要的校验。

更多精彩内容其他人还在看

简要解读Ruby面向对象编程中的作用域

作用域在面向对象编程中是一个十分重要的概念,程序构建时必须要理解清楚类和方法以及对象的作用范围,接下来就为大家简要解读Ruby面向对象编程中的作用域
收藏 0 赞 0 分享

详解Ruby中的instance_eval方法及其与class_eval的对比

Ruby的eval族方法将字符串作为代码来执行,instance_eval方法便是其中之一,下面就来详解Ruby中的instance_eval方法及其与class_eval的对比
收藏 0 赞 0 分享

Ruby程序中正则表达式的基本使用教程

和Python与Perl一样,Ruby对正则表达式的支持也是相当好的,这里送出整理的Ruby程序中正则表达式的基本使用教程,需要的朋友可以参考下
收藏 0 赞 0 分享

Ruby on Rails所构建的应用程序基本目录结构总结

Ruby on Rails是Ruby世界中一家独大的Web开发框架,要掌握Rails程序的构建,对其目录结构的了解十分必要,下面就来看一下Ruby on Rails所构建的应用程序基本目录结构总结
收藏 0 赞 0 分享

Ruby中的gem包管理的使用及gem源搭建教程

RubyGems是Ruby世界中的包管理工具,gem命令使用起来就如同Linux中的apt与yum一样,也可以构建自己的gem源,下面就带大家一起来学习Ruby中的gem包管理的使用及gem源搭建教程
收藏 0 赞 0 分享

Linux下Redis数据库的安装方法与自动启动脚本分享

这篇文章主要介绍了Linux下Redis数据库的安装方法与自动启动脚本分享,自动启动脚本分别针对CentOS和Ubuntu系统来给出了编写示例,需要的朋友可以参考下
收藏 0 赞 0 分享

Ruby与Ruby on Rails框架环境搭建的简明教程

这篇文章主要介绍了Ruby与Ruby on Rails框架环境搭建的简明教程,包括RubyGems的升级与OpenSSL的支持等配置,需要的朋友可以参考下
收藏 0 赞 0 分享

Ruby编写HTML脚本替换小程序的实例分享

这篇文章主要介绍了Ruby编写HTML脚本替换小程序的实例分享,单纯使用Ruby中的字符串替换方法而没有涉及更复杂的正则表达式,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Ruby中的代码块对象Proc

在Ruby中一个代码块block不是对象,但可以用Proc来替代其作为对象进行操作,接下来我们就来详解Ruby中的代码块对象Proc
收藏 0 赞 0 分享

Ruby中的Proc类及Proc的类方法Proc.new的使用解析

用Proc类可以用Proc.new来创建一个Proc类,进而来操作块,这里我们就来进行Ruby中的Proc类及Proc的类方法Proc.new的使用解析.
收藏 0 赞 0 分享
查看更多