Ruby元編程構(gòu)造簡單優(yōu)雅解決方案
Ruby語言雖然比較新穎,其編寫方式和一些特性于其他常見語言不盡相同,但是一些編程語言特有的屬性是不會改變的,比如Ruby元編程。#t#
元編程并不是一個很新的概念,通常元編程被認(rèn)為是通過程序來生成程序,如果從這種意義上來考慮,那么lex和yacc以及JavaCC應(yīng)該都可以算是具有了元編程的概念,在Java中,元編程得到了廣泛的應(yīng)用。
但在Ruby中,Ruby元編程的使用變得相當(dāng)?shù)暮唵魏腿菀讓崿F(xiàn),使用Ruby語言本身來產(chǎn)生Ruby代碼,不需要借助外部的工具,著名的RoR框架就是建立在Ruby元編程的基礎(chǔ)上的??赡苣銓υ幊踢€沒什么概念,但是Ruby已經(jīng)內(nèi)建了元編程這種機制,所以很有可能,你在不知不覺中就已經(jīng)使用了Ruby元編程技術(shù)為你帶來的方便之處。如下面這段代碼:
- class Person
- attr_reader :name
- end
你肯定知道:name是和@name相關(guān)聯(lián)的,但是你不一定清楚它到底是怎么實現(xiàn)的,其實attr_reader方法的實現(xiàn)就是采用了Ruby元編程技術(shù),如下面的這段代碼:
- class Module
- def attr_reader(*syms)
- syms.each do |sym|
- class_eval %{def #{sym}
- @#{sym}
- end
- end
- end
- end
看了這段代碼,你應(yīng)該大概了解Ruby元編程的機制了吧,如果你現(xiàn)在還不了解,那么我建議你先認(rèn)真的學(xué)習(xí)一下Ruby的反射機制,然后再接下去看這篇帖子,因為下面介紹的內(nèi)容并不是一杯嬰兒奶粉。
在Ruby On Rails中,有一個OR映射層,就是動態(tài)的從一張關(guān)系表映射到一個對象,這主要由ActiveRecord類來實現(xiàn)。在OR映射模型中,將關(guān)系數(shù)據(jù)庫中的關(guān)系表映射到對象模型時,將關(guān)系表的表名映射到類名,表中的每一個元組映射到對應(yīng)于這個類的一個對象,元組的一個字段對應(yīng)于對象的一個屬性。
假如我們有一個保存職員基本信息的文件,文件的格式是這樣的:第一行是文件內(nèi)容的每個字段的名稱,從第二行開始,則是每個職員的基本信息?,F(xiàn)在我們有一個文件名為“employee.txt”的文件,其內(nèi)容如下所示:
- name,age,gender
- "John", 23, "male"
- "Linclon", 25, "male"
假設(shè)我們就要從這個文本文件中讀取數(shù)據(jù),并進(jìn)行一定的處理。如果是使用C++編程,你首先一定會想到應(yīng)該定義一個Employee類,然后這個類中有name, age, gender這些成員變量。但是采用這種方法的話,可以發(fā)現(xiàn),如果想在職員信息中加入一個字段,比如部門(department),就不得不修改Employee類的代碼,在Employee類中增加一個“department”成員變量,所以我們的代碼是高度依賴于文件的具體格式,這當(dāng)然不是一個好的現(xiàn)象。
我們希望有一種更簡單和優(yōu)雅的方案,還有,Ruby動態(tài)性提高給我們一個解決方案,但是,我們應(yīng)該從何下手呢,這就需要Ruby元編程能力。
首先,我們想應(yīng)該有一個職員類,在Rails中,每個關(guān)系表的名稱會成為類的名稱,在這里,采用類似的方法,將文本文件的名稱作為類的名稱,在Ruby中,類名同時也是一個常量名,所以第一個字母必須為大寫,我們使用如下的代碼來生成類名。
- class_name = File.basename
(file_name, ".txt").capitalize- # "employee.txt" => "Employee"
- klass = Object.const_set
(class_name, Class.new)
Class.new生成一個新的類,這個類的名稱是匿名的,所以采用const_set操作來綁定一個類名,變量klass是新類型的引用。
生成了這個類以后,需要想這個類添加姓名,年齡和性別這些屬性,這些屬性的名稱是在文本文件的的第一行中給出的。
- data = File.new(file_name)
- header = data.gets.chomp
- data.close
- names = header.split(",")
下面的Ruby元編程代碼給出了如何生成這些屬性,以及初始化這些屬性值。
- klass.class_eval do
- attr_accessor *names
- define_method(:initialize)
do |*values|- names.each_with_index
do |name, i|- instance_variable_set
("@" + name, values)- end
- end
- #...
- end
現(xiàn)在,有了一系列的訪問子(可讀和可寫),通過instance_variable_set方法,又給每個屬性做了初始化。
變量names是在塊外部定義的,由于塊的閉合性,所以變量names在塊中也是有效的。當(dāng)然,為了Ruby元編程程序的演示,又定義的了一個to_s方法,代碼如下所示:
- define_method(:to_s) do
- str = "<#{self.class}: "
- names.each {|name| str <<
"#{name}=#{self.send(name)} "}- str + ">"
- end
- alias_method :inspect, :to_s
完成了這些以后,對于類的構(gòu)造已經(jīng)基本結(jié)束了,現(xiàn)在就需要真正的從文本文件中讀取數(shù)據(jù)了。從文本文件讀數(shù)據(jù)應(yīng)該是一個類方法,而不是一個實例的方法,其實現(xiàn)代碼如下:
- def klass.read
- array = []
- data = File.new(self.
to_s.downcase + ".txt")- data.gets #throw away header
- data.each do |line|
- line.chomp!
- values = eval("[#{line}]")
- array << self.new(*values)
- end
- data.close
- return array
- end
在這個方法中,使用字的類名來匹配相關(guān)的文件,比如將Employee類映射到“employee。txt”。然后,從文件中讀取職員信息,由于第一行是字段定義,所以要舍棄第一行數(shù)據(jù)。從第二行開始讀取數(shù)據(jù),每讀取一行數(shù)據(jù),則構(gòu)造一個Employee實例。通過上面這個簡單的例子,我們可以看出Ruby元編程的功能是相當(dāng)之強大的,使用元編程技術(shù),可以構(gòu)造相當(dāng)簡單,優(yōu)雅的解決方案。