Ruby元编程-Week-2
来源:互联网 发布:blog与mysql的分离 编辑:程序博客网 时间:2024/04/30 14:35
更多文章欢迎来沈小黑的菜园转转啦啦啦~~
解决代码重复
在星期二,书中给出了一个关于包装老系统接口造成代码冗余的例子。下面是这个例子,它贯穿了整个章节,集中体现了Ruby道路的优越性+_+
有一个老系统,他有很多蹩脚的代码,现在要求系统自动为超过99美元的开销添加标记。
蹩脚的代码是这样的:
class DS def initialize #连接数据源 def get_cpu_info(workstation_id) def get_cpu_price(workstation_id) def get_mouse_info(workstatin_id) def get_mouse_price(workstation_id) def get_keyboard_info(workstation_id) def get_keyboard_price(workstatin_id) def get_display_info(workstation_id) def get_display_price(workstatin_id)
真够蹩脚的= =如果用简单的包装方法来完成这个需求的话,代码就会变成这样:
class Computer def initialize(computer_id, data_source) @id = computer_id @data_source = data_source end def mouse info = @data_source.get_mouse_info(@id) price = @data_source.get_mouse_price(@id) result = "Mouse: #{info} ($#{price})" return "* #{result}" if price >= 100 result end #cpu, keyboard等都是相似的代码 #.... end
原谅我照抄书上的代码吧,要我写我也这么写+_+
呐~我们发现这些不同的部件代码都是相似的~怎么解决这个问题呢?首先祭出Ruby中的动态方法。
动态方法
动态方法分成两个部分:动态调用方法和动态创建方法。
动态调用方法
我们可以发现,这里的调用方法名其实是相似的,改变的只是 “get_#{设备名}_price” 中的facility。那么有没有办法可以直接把需要的设备名包装成 “get_#{设备名}_price” 的方法呢?
其实调用方法实际上是给一个对象发送消息,而Ruby正提供了一个send方法来支持通过发送消息的方式调用方法。
把这个技巧运用到Computer类中:
class Computer def initialize(computer_id, data_source) @id = computer_id @data_source = data_source end def mouse component :mouse end def cpu component :cpu end def keyboard component :keyboard end def component(name) info = @data_source.send "get_#{name}_info", @id price = @data_source.send "get_#{name}_price", @id result = "#{name.capitalize}: #{info} ($#{price})" return "* #{result}" if price >= 100 result endend
看起来简单了不少,至少所有的调用逻辑都放在同一个component方法里面了。但是仍然感觉有点不足:mouse、cpu、keyboard方法几乎都没有做什么事情,只是调用了component方法,这显得很多余。怎么解决这个问题呢?现在轮到动态创建方法登场了。
其实用send需要注意的一点是它可以调用private方法,如果确定只需要调用public方法的话,最好还是public_send方法吧。
动态创建方法
如果说动态调用方法在Java的语言机制中还能通过一定步骤实现的话,动态创建方法应该是Ruby的“独门绝技”了~Ruby可以通过Module#define_method方法随时定义一个方法,只需要提供方法名和充当方法主体的块。
再把动态创建方法糅合进Computer类中:
class Computer def initialize(computer_id, data_source) @id = computer_id @data_source = data_source end def self.define_component(name) define_method name do info = @data_source.send "get_#{name}_info", @id price = @data_source.send "get_#{name}_price", @id result = "#{name.capitalize}: #{info} ($#{price})" return "* #{result}" if price >= 100 result end end define_component :mouse define_component :cpu define_component :keyboardend
这样就不用一个个定义方法了,只需要在类中增加一条代码就会生成一个对应的方法,看起来又好了很多。
但是还是有问题:假如哪天采购部心血来潮新加了个数位板什么的,我们还要修改Computer类,这样耦合性是不是就有点高了?也许我们可以把DS类中所有”get_#{设备名}_price”格式的方法都读取出来,然后对应地创建动态方法,这就用到了Ruby的内省特性。
利用内省优化
class Computer def initialize(computer_id, data_source) @id = computer_id @data_source = data_source data_source.public_methods.grep(/^get_(.*)_info/){ Computer.define_component $1 } end def self.define_component(name) define_method name do info = @data_source.send "get_#{name}_info", @id price = @data_source.send "get_#{name}_price", @id result = "#{name.capitalize}: #{info} ($#{price})" return "* #{result}" if price >= 100 result end endend
很完美,那么还有其他的方法么?
幽灵方法
在Ruby中,假如调用了一个找不到的方法,就会触发一个类似异常的,叫做method_missing的方法。所有找不到的方法都会跑到这里,那我们就可以通过修改method_missing来实现动态代理,做到神不知鬼不觉地在某些情况下完成某些功能。比如,我们根本没有定义cpu方法,但是在method_missing中增加了“如果找不到的方法名是cpu,那么返回cpu信息”的逻辑,那么依然可以实现我们的需求,而不需要定义任何新方法。
利用幽灵方法重构Computer
class Computer def initialize(computer_id, data_source) @id = computer_id @data_source = data_source end def method_missing(name) super if !@data_source.respond_to?("get_#{name}_info") #如果是其他的未定义方法,那就给默认的method_missing处理 info = @data_source.send "get_#{name}_info", @id price = @data_source.send "get_#{name}_price", @id result = "#{name.capitalize}: #{info} ($#{price})" return "* #{result}" if price >= 100 result endend
respond_to_missing?方法
如果调用Computer.respond_to?(:mouse) 它会返回false,因为根本就没有定义这个方法嘛~那假如想要让幽灵方法中处理的情况也能体现在respond_to?方法中呢?我们不必修改respond_to?方法,而可以修改respond_to_missing?方法。因为respond_to?会检查respond_to_missing?,如果返回为true的话,就可以判定为true了。
所以在这里,我们需要把respond_to_missing?改写:
class Computer #... def respond_to_missing?(method, include_private = false) @data_source.respond_to?("get_#{method}_info") || super endend
P.S. 还有一个const_missing?方法,作用跟method_missing?类似,只是处理的是常量找不到的问题。如Rake中为兼容而允许在后续版本中使用先前没有命名空间的老名字,就是这么实现的:
class Module def const_missing?(const_name) case const_name when :Task Rake.application.const_warning(const_name) Rake::Task when :FileTask Rake.application.const_warning(const_name) Rake:: FileTask when :FileCreationTask #... endend
幽灵方法的陷阱
无限循环的bug
如果我们写了如下一个程序:
class Roulette def method_missing(name, *args) 3.times do number = rand(10)+1 end "#{name} got a #{number}" endend
这个程序会执行出无限循环最终崩溃的结果,为什么会这样呢?
number在循环体外使用了,这时候Ruby会认为这是一个方法,然而又找不到这个方法,这时候就会再次触发method_missing方法,如此循环下去,就会不断触发,最终导致崩溃。
这就是幽灵方法的第一个陷阱:在method_missing中的未定义方法会导致无限循环直至崩溃。
白板类
幽灵方法的另一个陷阱在于:它只有在方法找不到的时候才会被调用,所以假如祖先类或者自己后来增加了这个方法,那么就不会触发method_missing,也就会导致幽灵方法失效。
为了解决这个问题,Ruby提供了白板类BasicObject,它只有很少的几个实例方法:
p BasicObject.instance_methods#=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
如果需要白板类,可以直接从BasicObject继承。
在某些情况下可能需要自己决定删除什么方法,这时候可以使用Module#undef_method或者Module#remove_method,前者会删除包括继承而来的所有方法,而后者只会删除接收者自己的方法,而保留继承的方法。
现在,我们也许应该让Computer继承BasicObject了:
class Computer<BasicObject # ... codesend
小结
大多数情况下,幽灵方法都不如动态方法来得好,因为它并不是真正的方法,而只是类似异常的一个功能,使用它会导致诸如难以调试、方法被定义等问题。在能使用动态方法完成需求的场景下,我们都应该优先考虑动态方法而不是幽灵方法。当然,也有很多情况下不得不使用幽灵方法,那时候就是幽灵方法大显神威挥起它的镰刀的时候啦~~
- Ruby元编程-Week-2
- Ruby元编程-Week-1
- Ruby元编程-Week-3
- Ruby元编程-Week-4
- Ruby元编程-Week-5
- [读书笔记]Ruby 元编程2
- ruby元编程2------method
- ruby元编程
- ruby元编程记录
- Ruby 元编程 方法
- Ruby 元编程
- 《ruby 元编程》读书笔记
- 读《Ruby 元编程》
- Ruby 元编程 方法
- ruby元编程读后感
- 《Ruby 元编程》笔记
- Ruby元编程
- Ruby元编程
- getParameter 与 getAttribute的区别
- C++开发人脸性别识别教程(8)——搭建MFC框架之读取文件夹信息
- 搜狗微信公众号文章抓取
- 20点提高网站访问速度缩短网页加载时间
- vim 中如何进行列编辑。
- Ruby元编程-Week-2
- json 与 string 转化
- Web性能压力测试工具之WebBench详解
- 3-7-队列的链式存储-栈和队列-第3章-《数据结构》课本源码-严蔚敏吴伟民版
- Ruby元编程-Week-3
- pip install 出现报asciii码错误的问题
- Linux CLI操作常用快捷键
- 岁月划过生命线——大二上
- PAT练习基础编程题目之 厘米换算英尺英寸