在理解了方法查找的基础上,可以在运行时创建方法、插入方法调用、把调用转发给其他对象,甚至调用一个不存在的方法。接下来的元编程技术讲解了如何实现这些操作。
本章的主要内容:
通过元编程技术使代码更加简洁,更有利于扩展和维护。
先来看一段代码,通过重构的过程,逐步了解元编程怎样发挥作用。
- 这是一个连接数据库并查询相关信息的
data_source类。为节省时间,可直接跳到第三步重构步骤,需要时再跳回来看。
|
|
- 这是一个
computer类。
|
|
- 现在我们使用元编程的技术对
Computer类增加动态派发,进行第一次重构:
|
|
首先把重复的代码提取到一个方法中,然后把mouse,cpu,keyboard方法代理到component方法上。component方法会接着调用DS类的get_XXX_info和get_XXX_price方法。然后执行相关操作。
现在重复代码被消灭了很多,但仍然可以进一步精简。
什么是动态派发?
在代码运行的最后一刻再决定调用哪个方法,被称为动态派发。比如@datasource.send “get#{name}_info”, @id的使用。
- 现在我们使用
define_method方法来动态创建方法,以进行第二次重构:
|
|
define_method可以代替def关键字定义方法,这次重构中,self.define_component(name)方法执行了三次,每一次又都调用define_method分别创建了mouse ,cpu,keyboard方法。然后执行相关操作。
这三个在运行时被定义的方法被称为动态方法。
接下来又有一个关于代码维护和拓展的问题:如果将来DS类中加入了get_display_info方法,而Computer类中并没有动态派发这个方法,能不能在不修改Computer代码的基础上,自动将get_display_info方法加入到Computer的动态派发中呢?
- 用内省的方式第三次重构:
|
|
这次重构中新加入的data_source.methods.grep(/^get_(.*)_info$/){ Computer.define_component $1 }的作用如下:
当程序执行到puts computer.mouse时,会去Computer类中去找mouse方法,在initialize中,data_source.methods.grep(/^get_(.*)_info$/)如果找到get_mouse_info方法就会用mouse作为参数传给Computer.define_component,随后Computer.define_component会创建mouse方法。
上面的三次重构用到了动态派发和动态方法的技术。接下来是运用幽灵方法和动态代理来解决代码重构的问题。
- 第四次重构:
|
|
当程序执行到puts computer.mouse时,因为computer中没有直接定义mouse方法,所以会调用method_missing方法,如果在@data_source中找到了get_mouse_info,就会用mouse作为参数动态派发get_mouse_info和get_mouse_price执行相应程序返回相应结果。
什么叫幽灵方法?
method_missing被称作幽灵方法,它可以动态地创建方法。什么叫动态代理?
一个捕获幽灵方法调用并把它们转发给另外一个对象的对象(有时也会在转发前后包装一些自己的逻辑,在这里指自己重写的method_missing方法),称为动态代理(Dynamic Proxy)。
当一个幽灵方法和一个真实方法发生名字冲突时
这个问题是动态代理技术的通病,当一个幽灵方法和一个真实方法发生名字冲突时,后者会胜出。为了解决这个问题,你可以通过继承白板类和删除重名方法的方法来定义方法。
继承白板类:
|
|
删除重名方法:
可以使用Module#undef_method()方法,它会删除所有的(包括继承来的)方法;也可以使用Module#remove_method()方法,它只会删除接收者自己的方法,而保留继承来的方法。