2007年4月21日 星期六

使用MetaProgramming 動態定義類別

使用MetaProgramming寫程式的確和以往有很多不一樣的地方,例如,我可以在程式中讀取資料庫中的表格名稱和欄位名稱,然後根據表格的Schema動態產生一個類別定義,並且利用此類別產生Instance,像下面這樣:(注:為求簡化範例,資料庫存取的部份用模擬的)


class MetaProgrammingTest

# class method for creating a new class
def self.create_class(class_name, attr_names)
# use class name to create a class
klass = Object.const_set(class_name,Class.new)

klass.class_eval do
attr_accessor *attr_names

define_method(:initialize) do |*values|
attr_names.each_with_index do |name,i|
instance_variable_set("@"+name, values[i])
end
end

define_method(:to_s) do
str = "[#{self.class}:"
attr_names.each {|name| str << " #{name}=#{self.send(name)}" }
str + "]"
end

end

# return the class
klass
end

end


class TestDBSchemaReader
def self.getTableNameFromDB
# 下列程式可以改成從資料庫取得Table Name
return "Person"
end

def self.getAttributeNamesFromDB(table_name)
# 下列程式可以改成從資料庫取得Table的所有欄位名稱
return ["name", "age"]
end
end

table_name = TestDBSchemaReader.getTableNameFromDB

attr_names = TestDBSchemaReader.getAttributeNamesFromDB(table_name)

# 動態產生一個新的類別
myCls = MetaProgrammingTest.create_class(table_name, attr_names)

# 動態產生一個新的類別的Instance
vo = myCls.new("richard", 15)

puts "class name=" + vo.class.name
puts "instance: vo.name = " + vo.name + ", vo.age=" + vo.age.to_s



執行結果像這樣

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
>Exit code: 0


類別"Person"被動態的定義了,而且還使用此類別建立了Instance,蠻容易的,不是嗎?

更厲害的是,動態定義的類別還可以被繼承,像下列這段,我在程式後方又加上了一個GoodPerson類別,繼承自動態定義出來的Person類別:

class MetaProgrammingTest

# class method for creating a new class
def self.create_class(class_name, attr_names)




#更神奇的是,動態定義的類別還可以被繼承
class GoodPerson < Person
def hello
print "我是#{@name},我今年#{@age},我被發好人卡了!\n"
end
end

gp = GoodPerson.new("steve", 31)


執行結果

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
我是steve,我今年31,我被發好人卡了!
>Exit code: 0


更進一步的,我還可以在這個GoodPerson類別中,動態加入新method:

...
...

procBlock = proc { |x| puts "the " + x + "is a nerd!" }

GoodPerson.class_eval do
define_method(:showNerd, procBlock)
end

gp.showNerd "jonny"



執行結果

>ruby meta_programming_test.rb
class name=Person
instance: vo.name = richard, vo.age=15
我是steve,我今年31,我被發好人卡了!
the jonnyis a nerd!
>Exit code: 0

沒有留言: