什麼是超程式設計?
臨下班的緊急任務
時鐘指向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嗎?Employee 不就是和資料表對映的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以後再回來和你討論吧。”