- 浏览: 203339 次
- 来自: ...
文章分类
最新评论
-
赤道螞蟻:
如果是數據庫有定時任務,定時更新表的數據。 表中數據變化時,主 ...
用socket.io实现WebSocket的一个简单例子 -
cwalet:
在世界的中心呼喚愛 写道提示找不到 expressnpm in ...
用socket.io实现WebSocket的一个简单例子 -
在世界的中心呼喚愛:
提示找不到 express
用socket.io实现WebSocket的一个简单例子 -
Anleb:
def m1(a)
puts 'invoke m1'
pu ...
Ruby的一些疑问 -
biyeah:
补充,任何类,只要实现to_proc方法,都可以与&结 ...
Ruby的一些疑问
在Ruby中对字符串和block求解
作者:Jay Fields 原文:http://tech.it168.com/d/2007-09-07/200709071737579.shtml
介绍
对包含代码的字符串和block求解,是我最钟爱的Ruby特性之一。Ruby提供了多种不同类型的求解方式;不过我最常用的是下面这些:eval、instance_eval和class_eval。
Module.class_eval
使用Module类的class_eval(及其别名module_eval)方法,可以在一个类的定义或者module定义的上下文中对给定字符串或block进行求解。我们常常用class_eval来向类的定义中加入方法,或是包含其他的module。
require "erb" klass = Class.new klass.class_eval do include ERB::Util def encoded_hello html_escape "Hello World" end end puts klass.new.encoded_hello #=> Hello World
不使用class_eval也可以达到上面的效果,但是要牺牲代码的可读性。
require "erb" klass = Class.new klass.send :include, ERB::Util klass.send :define_method, :encoded_hello do html_escape "Hello World" end klass.send :public, :encoded_hello puts klass.new.encoded_hello #=> Hello World
Object.instance_eval
使用Object的instance_eval方法,可以在一个类实例的上下文中对给定字符串或block进行求解。这是个功能强大的概念:你可以先在任何上下文中创建一块代码,然后在一个单独的对象实例的上下文中对这块代码进行求解。为了设定代码执行的上下文,self变量要设置为执行代码时所在的对象实例,以使得代码可以访问对象实例的变量。
class Navigator def initialize @page_index = 0 end def next @page_index += 1 end end navigator = Navigator.new navigator.next navigator.next puts navigator.instance_eval "@page_index" #=> 2 puts navigator.instance_eval { @page_index } #=> 2
与使用class_eval的示例类似,实例变量的值可以通过其他的方式获取,不过使用instance_eval是一种非常直观的做法。
Kernel.eval
使用Kernel的eval方法可以在当前上下文中对一个字符串求解。可以选择为eval方法制定一个binding对象。如果给定了一个binding对象,求解的过程会在binding对象的上下文中执行。
hello = "hello world" puts eval("hello") #=> "hello world" proc = lambda { hello = "goodbye world"; binding } eval("hello", proc.call) #=> "goodbye world"
扩展eval的上下文
第一次使用eval,我用它来创建了attr_init这个类方法。当时我发现我总是在重复下面代码中的模式:
def some_attribute @some_attribute || = SomeClass.new end
因此我决定创建一个类方法来封装上面的行为:
class << Object def attr_init(name, klass) define_method(name) { eval "@#{name} ||= #{klass}.new" } end end
记得当时我觉得这样调用eval是非常丑陋的做法,但那会儿我想不出更好的方式来实现这样的效果;因此我把代码贴到了博客中,等待别人的指摘;他们很快就做出了回应,并给出下面的做法。一开始我并没有觉察这样做的好处,但是后来我意识到这个解法是非常出色的:它只需要调用一次eval方法,而不是在每次进行方法定义时都去重新调用eval。
class << Object def attr_init(name, klass) eval "define_method(name) { @#{name} ||= #{klass}.new }" end end
这样优化的有趣之处在于:它需要求解更多的内容, 以达到提升运行效率的目的。从那时开始,我只在必要的时候才使用eval,而且我非常注意如何以更有效率的方式来使用eval。
在不同上下文中使用instance_eval
在不同上下文中,对block或是以字符串形式出现的代码进行求解是很有价值的一种做法,也是设计领域特定语言(Domain Specific Language,DSL)时很常用的一种技术。实际上,在多种上下文环境中进行求解的能力是使用DSL的一个关键因素。请看下面的代码:
class SqlGenerator class << self def evaluate(&script) self.new.instance_eval(&script) end end def multiply(arg) "select #{arg}" end def two(arg=nil) "2#{arg}" end def times(arg) " * #{arg}" end end
使用上面的代码,调用SqlGenerator.evaluate方法并给定一个block参数,便可以生成一条SQL语句:
SqlGenerator.evaluate { multiply two times two } => "select 2 * 2"
然而,你还可以在一个calculator类的上下文中执行同样的代码来获得结果:
class Calculator class << self def evaluate(&script) self.new.instance_eval(&script) end end def multiply(arg) eval arg end def two(arg=nil) "2#{arg}" end def times(arg) " * #{arg}" end end
执行结果:
Calculator.evaluate { multiply two times two } => 4
上述代码展示了如何使用instance_eval来指出block执行的作用范围。我在前面提到过,instance_eval方法在接受者的上下文中对字符串或block展开求解。例子中的接收者是SqlGenerator的一个实例和Calculator的一个实例。同时要保证使用self.new.instance_eval这样的方式来调用。如果不调用self的new方法,会将block作为类的一部分进行求解,而不是在类的实例中完成。
上述代码同样展示了开始定义DSL所需的一些步骤。创建DSL是很有挑战性的工作,但同时会带来很多好处。通过DSL来表达业务规则,所带来的好处是可以在多种上下文中执行这些业务规则。如上述示例所展示的,通过在不同上下文中执行DSL,可以从同一个业务规则产生多种不同的行为。当业务规则随着时间推移而改变时,系统中所有引用该业务规则的构成部分都会随之发生变化。而对Ruby求解方法的利用,就是成功实现这种效果的关键。
关于DU场中扑克牌桌的示例
Ruby提供的不同的求解方法,让我们可以很方便的在不同上下文中执行代码。举例来说,假设你为一个DU场工作,分派给你的任务是设计一个系统。当需要开一张新的扑克牌桌,或是需要知道等多久才能开新牌桌时,这个系统负责通知扑克牌室的工作人员。新开牌桌的业务规则,根据牌桌上的DU注大小和等待列表中的人数多少而不同。例如,对于一个DU注不封顶的牌局来说,牌桌边等待的人数多一些也无妨,因为人们更有可能在一手牌中输光他们所有的钱;如果贸然开新的牌桌,由于没有足够的玩家,该牌桌可能很快就要关闭。规则在DSL中可能以下面的方式表示:
引用
if the '$5-$10 Limit' list is more than 12 then notify the floor to open
if the $1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
if the $1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
第一个执行DSL的上下文被用来通知DU场雇员。代码如下:
class ContextOne < DslContext bubble :than, :is, :list, :the, :to def more(value) '> ' + value.to_s end def method_missing(sym, *args) @stakes = sym eval "List.size_for(sym) #{args.first}" end def floor(value) __position(value, :floor) end def brush(value) __position(value, :brush) end def open __action(:open) end def announce __action(:announce) end def __action(to) {:action => to} end def __position(value, title) value[:position] = title value end def notify(value) [@stakes, value] end end
ContextOne通过下面的代码执行。
script = <<-eos if the '$5-$10 Limit' list is more than 12 then notify the floor to open if the '$1-$2 No Limit' list is more than 15 then notify the floor to open if the '$5-$10 Limit' list is more than 8 then notify the brush to announce if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce eos class Broadcast def self.notify(stakes, options) puts DslContext.sym_to_stakes(stakes) options.each_pair do |name, value| puts " #{name} #{value}" end end end ContextOne.execute(script) do |notification| Broadcast.notify(*notification) end
ContextOne继承自DslContext。DslContext的定义如下。
class DslContext def self.execute(text) rules = polish_text(text) rules.each do |rule| result = self.new.instance_eval(rule) yield result if block_given? end end def self.bubble(*methods) methods.each do |method| define_method(method) { |args| args } end end def self.polish_text(text) rules = text.split("\n") rules.collect do |rule| rule.gsub!(/'.+'/, extract_stakes(rule)) rule << " end" end end def self.extract_stakes(rule) stakes = rule.scan(/'.+'/).first stakes.delete!("'").gsub!(%q{$}, 'dollar').gsub!('-', 'dash').gsub!(' ', 'space') end def self.sym_to_stakes(sym) sym.to_s.gsub!('dollar', %q{$}).gsub!('dash', '-').gsub!('space', ' ') end end
ContextOne的method_missing方法中使用了List类,List类代码如下。
class List def self.size_for(stakes) 20 end end
ContextOne使用DSL检查每张牌桌的List大小,并在必要的时候发送通知。当然,这只是演示代码,List对象也只不过是stub,以验证ContextOne和DslContext所有的功能都没有问题。这里要重点注意:方法的执行被委托给了instance_eval,这样才能在ContextOne的上下文中对代码进行求解。
同样的脚本,可以在第二个上下文中执行;这个上下文返回当前正在散播的不同类型的DU*博游戏。
class ContextTwo < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :floor, :open, :brush def announce @stakes end alias open announce def method_missing(sym, *args) @stakes = sym end end
正像我们看到的,添加新的上下文是非常方便的。由于DslContext的execute方法调用instance_eval方法,上面的代码可以如下的方式执行。
ContextTwo.execute(script) do |stakes| puts ContextTwo.sym_to_stakes(stakes) end
为了使我们的示例更加完整,我们创建另外一个例子,显示所有接收通知的位置。
class ContextThree < DslContext bubble :than, :is, :list, :the, :to, :more, :notify, :announce, :open, :open def announce; end def open; end def brush(value) :brush end def floor(value) :floor end def method_missing(sym, *args) true end end
同样的,这个上下文也继承自使用了instance_eval的DslContext,因此,只要运行下面的代码来执行即可。
ContextThree.execute(script) do |positions| puts positions end
在多个上下文中对DSL进行求解的能力,模糊了代码和数据之间的界线。可以对脚本‘代码’进行求解来生成报表(比如关于系统中已联系雇员的报表)。在展示需要多久才会新开扑克牌桌这样的上下文中,也可以对脚本进行求解(比如,业务规则说明需要15个人才能新开一张牌桌,系统知道在等待列表中有10个人,因此显示“5 more people needed before the game can start”)。使用instance_eval,我们可以在系统需要的任何上下文中,对同样的代码进行求解。
同样具有魔法的eval
上述代码展示的是:如何在不同的作用范围中,使用instance_eval对block进行求解。不过,eval方法同样可以在不同的上下文中进行求解操作。下面我来展示如何在block的作用范围中对ruby代码构成的字符串进行求解。
先让我们从一个简单的例子开始,不过让我们先回顾一下如何根据block的binding使用eval。我们需要一个能够帮我们创建block的类。
class ProcFactory def create Proc.new {} end end
在示例中,ProcFactory类有一个方法:create;它的功能只是简单地创建并返回了一个proc对象。尽管这看起来似乎没什么特别之处,但我们可以在proc对象的作用范围中,使用它对任何包含ruby代码的字符串进行求解。这样,我们不需要直接引用某个对象,便可以在这个对象的上下文中求解ruby代码。
proc = ProcFactory.new.create eval "self.class", proc.binding #=> ProcFactory
什么时候会用到这样的功能呢?我最近在开发表示SQL的DSL时用到了它。我开始使用类似下面代码的语法:
Select[:column1, :column2].from[:table1, :table2].where do equal table1.id, table2.table1_id end
上述代码被求解时,跟在from后面的[]实例方法将所有的表名保存在一个数组中。接下来,当执行where方法时,传递给where的block会执行。此时,method_missing方法会被调用两次,第一次针对:table1,第二次针对:table2。在method_missing的调用中,对之前提到过的、用[]方法创建的表名数组进行检查,以查看标识符参数(:table1和:table2)是否为合法的表名。如果表名在数组中,我们返回一个知道如何应对字段名称的对象;如果表名非法,我们会调用super并抛出NameError。
应对一般的简单查询,上面的做法不存在问题;但如果涉及到子查询的话,就另当别论了。前述实现对下面示例中的代码是无效的。
Delete.from[:table1].where do exists(Select[:column2].from[:table2].where do equal table1.column1, table2.column2 end end
不过我们可以使用eval与指定的binding一起,让上面的代码正常工作。此处的技巧是:将表名数组从外部的block隐式地传递到内部的block中。用显式方式传递会让DSL看起来很丑陋。
在Select类的where方法中,我们使用block的binding对象来得到Delete实例的tables集合。我们能够这样做,在于Delete实例的where方法被作为上下文(亦即block的binding)传递给了select实例的where方法。binding对象(或上下文)是block被创建时的作用范围。下面的代码是对where方法的完整实现。
def where(&block) @text += " where " tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect instance_eval &block end
我们把eval所在的语句拆开看看它都干了什么。它做的第一件事情是:
eval "respond_to?(:tables) ? tables : []", block.binding
它的作用是“在block的作用范围中对语句进行求解”。在当前例子中,block的作用范围是:
Delete.from[:table1].where do .. end
这个范围是一个Delete类的实例,Delete类中确实有tables方法,其作用是暴露表名数组(tables#=>[:table1])。因此,语句被求解后会返回表名数组。剩余的语句就可以看作:
tables.concat([:table1])
此句只是将所有的表名加入到tables数组中,并且可以被内部的block访问。有了这一行代码的处理,我们就可以让子查询产生正确的结果了。
引用
delete from table1 where exists (select column2 from table2 where table1.column1 = table2.column2)
下面的代码可以产生上述结果,并且能够作为参考,以了解如何与binding一起使用eval。
class Delete def self.from Delete.new end def [](*args) @text = "delete from " @text += args.join "," @tables = args self end attr_reader :tables def where(&block) @text += " where " instance_eval &block end def exists(statement) @text += "exists " @text += statement end end class Select def self.[](*args) self.new(*args) end def initialize(*columns) @text = "select " @text += columns.join "," end def from @text += " from " self end def [](*args) @text += args.join "," @tables = args self end def tables @tables end def where(&block) @text += " where " tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect instance_eval &block end def method_missing(sym, *args) super unless @tables.include? sym klass = Class.new klass.class_eval do def initialize(table) @table = table end def method_missing(sym, *args) @table.to_s + "." + sym.to_s end end klass.new(sym) end def equal(*args) @text += args.join "=" end end
结语
正如我们所看到的那样,使用Ruby提供的多种求解方法,我们可以创建简练、可读的代码;这些求解方法同时提供了创建诸如领域特定语言之类强大工具的能力。
关于作者
Jay Fields是ThoughtWorks的一位开发人员。他总是在寻找令人兴奋的新技术,并愿意马上采用这些技术。他最近一段时间的工作中心放在领域特定语言(DSL)上面,所交付的应用为特定业务领域专家使用DSL撰写应用业务规则提供了强大的支持。
发表评论
-
在Rails中使用Pry
2012-02-07 06:43 2194Pry可看成是IRB的加强版。支持语法高亮等特点。 1、在Ge ... -
camping 一个小巧的ruby web framework
2012-02-04 04:01 1795https://github.com/camping/camp ... -
Markaby (Markup as Ruby)
2012-02-04 03:58 1183http://markaby.rubyforge.org/ ... -
Ruby的一些疑问
2012-01-26 01:01 14371、网点看到一断程序, def m1(a) puts 'i ... -
RSpec测试框架
2012-01-10 12:59 3000#参考http://www.slideshare.net/ih ... -
[转]Ruby - DUP vs CLONE
2012-01-09 12:57 1847http://railsblogger.blogspot.co ... -
[转]eval, class_eval, instance_eval和binding
2012-01-09 12:10 1109http://www.cnblogs.com/rubylouv ... -
[转]Ruby中的binding
2012-01-09 11:50 1606http://kkito.cn/index.php/blog/ ... -
[转]Method visibility in Ruby
2012-01-04 12:37 1179From:http://weblog.jamisbuck.or ... -
[转]浅谈Ruby on Rails中的include和extend
2011-12-30 02:20 1062http://developer.51cto.com/art/ ... -
[转]Ruby中的Rake任务详述
2011-12-29 03:47 1786Trackback: http://tb.blog.csdn. ... -
[转]ruby中的闭包
2011-12-26 10:42 2780原文: http://kenbeit.com/po ... -
ruby技巧
2011-12-26 10:40 0ruby小技巧之 http://kenbeit.com/pos ... -
[转]Ruby中的多态
2011-12-20 11:56 0原文http://kkito.cn/index.php ... -
ruby中实现闭包
2011-12-20 09:10 983ruby中实现闭包很简单 如果一个方法中返回一个proced ... -
ruby中星号的使用
2011-12-20 05:54 1955ruby中星号的使用 1、数字乘法 2 * 3 = 6 2 ... -
[转]ruby的include与extend
2011-12-20 05:09 910原文http://www.cnblogs.com/rubylo ... -
[转]Ruby常用的内部变量
2011-12-20 03:59 968Ruby常用的内部变量 原文http://www.cnblog ... -
ruby与javascript面向对象编程的比较
2011-12-06 14:05 3361原文:http://howtonode.org/object- ... -
解决ruby中文乱码
2011-12-06 04:20 8030在文件头加上#encoding=UTF-8,示例: #enco ...
相关推荐
标题中的“论坛转帖工具.rar”表明这是一个用于在论坛之间转移帖子的软件工具,通常用于帮助用户方便地将一个论坛的帖子内容复制到另一个论坛,可能是为了分享信息、讨论或保存重要的帖子。这类工具可能包括自动抓取...
【贴吧转帖工具】是一种专为百度贴吧用户设计的便捷工具,主要用于提高用户在贴吧中的互动效率。通过这款工具,用户可以实现一键转帖和一键8经验签到的功能,极大地简化了传统操作流程,节省了用户的时间,提升了...
- 使用Actual Search and Replace,将源代码中所有与"cheatengine"相关的字符串替换为其他无关联的字符串,确保生成的文件标题不再包含"cheatengine"。 - 在Delphi7中打开CE的源代码工程,编译生成新的EXE文件。 ...
在IT行业中,编辑人员在处理图像或视频时经常会遇到水印问题。水印可能是他人版权的标识,也可能是不希望展示的信息,去除水印成为了一项必要的技能。本篇文章将详细探讨“编辑人员转帖去水印工具”,并介绍如何使用...
UBB论坛转帖圣手.exeUBB论坛转帖圣手.exe
1. 要获取网页源代码,可以使用WebView的`evaluateJavascript()`方法,该方法允许在JavaScript环境中执行代码,并将结果作为字符串返回。可以编写一个JavaScript函数来获取页面的HTML内容: ```javascript function...
3. 性能影响:大量并发的发帖或转帖可能对论坛服务器造成压力,需合理控制频率,以免影响用户体验。 四、安全性与兼容性 1. 安全检测:在下载和使用此类工具时,需确保来源可靠,避免安装携带恶意软件的版本,保护...
标题和描述中的“世界编程大赛第一名写的程序”这一知识点,实际上指向了计算机科学与编程竞赛领域的一个重要概念:即在高水平的编程比赛中,优胜者所编写的代码往往蕴含着高级算法、数据结构以及编程技巧。...
2. **使用Word**:复制网页URL,然后在Word中打开,Word会下载网页内容,用户可以在Word中进行复制、编辑和保存。 3. **浏览器插件**:有些浏览器,如火狐,可以安装扩展程序(如Greasemonkey)来解除右键锁定。例如...
还有一个是以空格分隔的字符串倒序输出,比如:I am a student,输出结果为:student a am I.字符串的操作相对还是比较熟一点。还让写了两个函数,分别是动态实现二维数组,以及如何释放二维数组,记得当时在大学的...
本文将深入探讨“一键转帖功能插件”在帝国CMS 6.0系统中的应用与实现,该插件适用于GBK及UTF-8编码环境,旨在提升网站内容的分享与传播效率。我们将从安装步骤、工作原理、可能遇到的问题以及解决方案等方面进行...
8. **故障排查**:如果在使用过程中遇到问题,如插件不工作、报错等,用户可以查阅插件提供的文档、社区论坛或联系开发者寻求帮助。 总的来说,"转帖工具插件 for PHPwind 7.5 正式版" 提供了一个高效且用户友好的...
标题《【转帖】4412嵌入式开发板学习笔记(一)》和描述《新手在进行开发学习前,建议先看01-迅为电子开发板入门视频。对开发板和开发环境有一定的了解后,不要盲目接线开机。以下是个人的一点经验,和大家分享一下...
HTML2UBBMaxcj 是一款专为Softii论坛设计的转帖工具,它主要用于将HTML格式的帖子内容转换成UBB代码,以便在论坛中更好地显示和分享。UBB(Universal BBCode)是一种轻量级的标记语言,常用于网络论坛,与HTML类似,...
转帖PLCDCSFCS三大控制系统的特点和差异 PLC、DCS、FCS 三大控制系统是自动化技术中的热点,各有其特点和差异。下面对这三大控制系统的特点和差异进行分析。 1.PLC(Programmable Logic Controller) PLC 是一种...
总结一下,"一键转帖功能插件 for 帝国CMS v1.0" 提供了一种便捷的方式来增强帝国CMS站点的社交分享功能,通过简单的安装和配置,用户可以轻松地在多个平台上转发内容,有助于增加网站的曝光度和用户的参与度。...
在Android应用开发中,自动升级和更新功能是一个重要的特性,它允许用户无缝地获取应用的最新版本,提高用户体验。本文将详细介绍如何实现这样一个模块。 **一、基础概念** 1. **版本标识**: 在AndroidManifest.xml...
4.cookie记录替换和新增关键词(避免每次打开转帖工具都要输入繁琐的替换关键词) 5.新增按颜色屏蔽干扰码 6.新增减少缩进以及优化了首行缩进 7.优化一些小细节 8.重新布局优化显示及方便操作 9.不断更新中...
在SEO实践中,对原创内容的尊重不仅体现在维护内容的原创性和独特性上,还体现在对原作者和来源的认可上。这不仅是对版权的尊重,也是建立良好网络生态环境的基础。 总结而言,标题的更改是一个微妙的过程,尤其...
一个自己写的小工具,可以将C/C++代码进行转换,以方便在QQ空间里转帖程序代码。 ... 预览模块用的是codeguru里Anonytmouse的动态库,在此表示感谢。...有一些小的bug没有完善,目前只是高亮关键字、字符串、和注释。