转载

什么是元编程?

临下班的紧急任务

时钟指向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以后再回来和你讨论吧。”

(完)

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

原文  https://mp.weixin.qq.com/s/mXINFf5KkqQGePKywYRpTg
正文到此结束
Loading...