什么是元编程?

栏目: Ruby · 发布时间: 6年前

内容简介:临下班的紧急任务时钟指向6点半,张大胖今天不太忙,想着今天终于可以早点儿下班了。收拾好东西准备离开的时候,领导布置了一个新任务,张大胖很无奈,哀叹一声,老老实实地坐下来。

临下班的紧急任务

时钟指向6点半,张大胖今天不太忙,想着今天终于可以早点儿下班了。

收拾好东西准备离开的时候,领导布置了一个新任务,张大胖很无奈,哀叹一声,老老实实地坐下来。

新任务看起来非常简单:从一个CSV文件中读取数据,形成 Java 对象,然后对外提供一个API,让别人调用。

这个CSV文件叫做employee.csv, 张大胖打开这个CSV文件,里边的内容一看就懂。

name,age,level

Andy,25,B7

Joe, 22, B6

张大胖的API就需要返回一个List<Employee>,很自然,Employee类长这个样子:

public class Employee{
    private String name;
    private String age;
    private String level;
    ......
}

class中的每个字段和csv文件的“表头”的“列名”保持一致。

这样简单的任务对张大胖来说是小菜一碟,他写了一个EmployeeParser,专门解析CSV文件,形成Employee对象,半个小时不到就收工了,赶紧下班!

什么是元编程?

还没来得及溜走,又被领导叫住了:“大胖,那个CSV文件新加了一个字段,叫做salary ,快把你的程序改一下啊!”

name,age,level,salary

Andy,25,B7,3000

Joe, 22, B6,2500

张大胖极不情愿地坐下来,给Employee类增加了一个salary的字段,又修改了EmployeeParser类,增加对这个字段的解析。

然后又听到领导在喊:“又加了一个字段,叫做tax !”

没辙,继续修改Employee类和EmployeeParser类吧。 这一次修改完,领导终于放他走了。

模板:用程序来生成程序

等了两趟车,终于在西二旗挤上了13号线,张大胖心里一直在想:明天保不齐还要增加字段,这真是让人厌烦的重复劳动啊。大家都说, Don't repeat yourself , 我这怎么才能减少重复呢?

关键点就在于,那个Java类的字段要和CSV的表头的列名做对应,CSV变化了,Java类的字段以及解析的方法都要做相应得修改才可以。

对了,能不能根据CSV的列名 自动地 生成那个Employee类啊,这样问题不就解决了吗? CSV变化, Employee类跟着变化,多好!

CSV的“列名”经过读取,可以变成一个Java 的List ,例如["name","age","level"], 如何写一段代码,把这个List变成一个Employee Class呢?

张大胖聚精会神,在地铁上想了一路,完全无视地铁上那拥挤的人群和污浊的空气。

快要下车时,他灵机一动,可以用模板技术嘛,比如velocity模板,定义一个employee.vm :

public class Employee{    
    #foreach ($field in $headers)
        private String $field;         
    #end    
    ##其他代码略
}

然后再写一个代码生成器,读取employee.csv的“表头”,形成List,把List传递给这个employee.vm模板,就可以输出Java类了:

什么是元编程?

写成具体的代码就是这个样子:

VelocityEngine ve = new VelocityEngine();

...初始化引擎的代码略...

Template template = ve.getTemplate("employee.vm");   

VelocityContext context = new VelocityContext();

List<String> headers = readCSVHeaders();

context.put("headers",headers);

Writer writer = new PrintWriter(new FileOutputStream(
                new File("C:\\Employee.java")));           

//把headers变量传递给模板
template.merge(context, writer);
writer.flush();    

(友情提示:可左右滑动)

(码农翻身注:这里做了简化只关注了Empployee的字段,还需要处理getter/setter方法,尤其是也需要通过模板的方法生成EmployeeParser,用来形成Employee对象。此外还有数据类型的问题。)

在小区对面的田老师红烧肉吃了一份盖饭以后,张大胖立刻投入到程序的编写中来,一边写一边想: 我这是用程序来生成程序啊!

元编程

第二天,领导果然要加新的字段了,张大胖心中暗自佩服自己的自知之明,调出昨晚写的“宝贝”执行了一下,不到一秒钟,新的Employee和EmployeeParser就生成了。

下午的时候,张大胖洋洋得意地给Bill展示自己的工作成果,Bill说:“不错啊,都开始元编程了!”

“元编程?”

“对啊,你不是用程序来生成程序嘛,这就是一种元编程。”

张大胖没想到的工作居然就是高大上的“元编程”,更高兴了。

“还有,如果把CSV文件看成数据库的表,代码生成器自动生成的EmployeeParser不就相当于DAO吗?Employeeb 不就是和数据表映射的Domain对象吗? 你的代码实现了Object-relational mapping !”

就是啊,我怎么没想到,虽然距离真正的O/R Mapping还很远,但思想是一致的,大神就是厉害,看透了本质,张大胖暗想。

可是Bill很快给它泼了一盆冷水:“不过这种用模板生成的方式还是有些‘ 低级 ’,每次CSV文件有变化,都需要运行一下代码生成器才可以。”

“那怎么办?”

“其实吧, 这个Employee的类没有必要在编译期存在,如果能在运行时动态地生成就行了 。”

运行期动态生成? 张大胖有点懵。

“对于Java语言来说, 运行期在内存中动态生成一个Class ,还是有难度的,你需要透彻理解Java Class的文件格式,还需要在底层需要用ASM这样的东西去操作Java字节码。”

“文件格式和字节码?就是那些0xCAFEBABE,iload ,iadd, putfield,invokespecial ? ”  张大胖看过虚拟机的书,知道有很多字节码,但是操作它们形成符合要求的类,实在是难以想象。

Bill 笑道:“你可以用动态语言,比如Ruby,元编程很强大,实现你这个功能简直是小菜一碟。”

Bill很快就写出了一段代码:

#在内存中创建一个名称为Employee的类
klass = Object.const_set("Employee", Class.new) 

names= ...读取csv文件第一行,形成数组,如 ["name","age","level"]...

#对这个内存中的类进行"手术"
klass.class_eval do
    #现在 name,age,level...变成了这个Employee类的字段!
    attr_accessor *names    
    #再定义一个Employee类的构造函数    
    define_method(:initialize) do |*values|  
        names.each_with_index do |name, i|  
        instance_variable_set("@" + name, values[i])  
    end
    end 
end 

(友情提示:可左右滑动)

张大胖没有学过Ruby , 看到这里更懵了。

Bill看到张大胖发呆的样子,说道:”经过上述处理,内存中创建了一个类,如果把它的源码展示一下,你就明白了。”

#动态生成的类
class Employee
  #动态生成的属性,类似与java的getter方法
  def name
    @name
  end
  #动态生成的属性,类似java的setter方法
  def name=(str)
    @name = str
  end
  def age
    @age
  end
  def age=(str)
    @age = str
  end
  def level
    @level
  end
  def level=(str)
    @level = str
  end
  #动态生成的构造函数
  def initialize(*values)
      @name = values[0]
      @age = values[1]
      @level = values[2]
  end
end
#一个使用Employee类的例子
p = Employee.new("andy","22","B6")

(友情提示:可左右滑动)

(码农翻身注:对CSV文件内容的读取没有包括在其中。)

张大胖明白了,这个类是由数据驱动,动态生成的,CSV的header 中有多少字段,这个类就会生成多少个属性。

和自己的代码生成器比较了一下,Ruby写的这段代码更加精炼,不需要模板,没有所谓代码生成器,或者说,代码生成器和生成的类已经合二为一了。

即使是CSV文件发生了变化,也不需要额外运行代码生成器,只需要执行那段 Ruby 代码就行。

什么是元编程?

Bill问道:“怎么样,元编程不错吧?”

张大胖说道:“嗯, 这Ruby的元编程能力很强大啊,可惜的是,我们的项目都是Java的,这动态的脚本语言Ruby没法直接使用,如果是微服务,对外提供的是HTTP的API,我可以学学Ruby,单独写个Ruby项目。”

Bill说:“其实吧,编程语言中,元编程能力最强大的还属LISP,在LISP当中,程序和数据的表现形式是一致的,造就了它无以伦比的元编程能力,LISP程序可以像操作数据一样操作代码。 有人甚至说,LISP根本不是编程语言,它是编程元语言,专门为了生成程序而生。”

张大胖听得云里雾里,黯然道:“不知道你在说什么,太抽象了!等我学学LISP以后再回来和你讨论吧。”

(完)

码农翻身,用故事讲解技术本质, 更多精彩文章,请移步《 码农翻身三年文章精华


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rationality for Mortals

Rationality for Mortals

Gerd Gigerenzer / Oxford University Press, USA / 2008-05-02 / USD 65.00

Gerd Gigerenzer's influential work examines the rationality of individuals not from the perspective of logic or probability, but from the point of view of adaptation to the real world of human behavio......一起来看看 《Rationality for Mortals》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试