到目前為止,我們的《Groovy探索之MOP》系列已經談到了使用ExpandoMetaClass的方方面面,但值得注意的是,我們通過ExpandoMetaClass給一個類在運行期內添加一個方法,不管是普通方法還是靜態方法,我們都是添加一個確定方法名的方法。即我們添加一個方法名為A的方法,然後才能使用這個方法A。
然而,方法名的動態性,其實是我們早已接觸過的事情,比如在《Groovy探索之invokeMethod方法》裡,我們就可以創建形如“sortByXxxxx()”這樣漂亮的方法。當然了,在那篇文字裡,我們是通過“invokeMethod”方法來實現的。
我們都知道,使用ExpandoMetaClass在運行期內給一個對象或類添加一個方法要比hook方法簡單得多,所以,我們在實際的編碼過程中,更多時候會用到ExpandoMetaClass來實現。那麼,我們使用ExpandoMetaClass是否也可以實現上面hook方法所實現了的方法名的動態性呢?
我們知道,使用ExpandoMetaClass在運行期內添加一個方法(不管是給對象還是給類),形式都是像下面的樣子:
類名.metaClass.方法名 = 方法體
同時,上面的公式還可以寫成如下的樣子:
類名.metaClass. "方法名" = 方法體
這下我們明白了,既然方法名可以用雙引號括起來,那麼,如果雙引號裡面是一個Gstring對象的話,這樣,我們就可以給方法賦予一個可變的數據了。從而達到了我們使用ExpandoMetaClass實現方法名的動態性的目的了。
廢話少說,我們還是先來看一個例子吧!
現在,我們有一個T類,如下:
class T
{
}
我們就可以這樣對T類添加方法:
String functionName = "a";
T.metaClass."$functionName" = {
println "invoke $functionName"
}
def t = new T()
t.a()
我們在添加方法的時候,使用了一個Gstring對象來代替了寫死的字符串,這樣就實現了方法名的動態性。運行結果為:
invoke a
當然了,你可能會覺得上面的方法名的動態性還不夠清晰,那麼,我們可以實現如下的一個靜態方法,來達到我們方法名動態性的目的:
def static add(functionName)
{
T.metaClass."$functionName" = {
println "invoke $functionName"
}
}
這樣,我們就可以如下來使用它了:
add('b')
def t1 = new T()
t1.b()
add('c')
def t2 = new T()
t2.c()
運行結果為:
invoke b
invoke c
上面的例子只是小兒科,我們使用ExpandoMetaClass實現的方法名的動態性同樣可以使用於《Groovy探索之invokeMethod方法》中的那個有名的例子。關於那個例子的來歷,大家可以在原文中看到,這裡就不再重述。
在那篇文字中,Student類是這樣的:
class Student {
String no;
String name;
float chinScore;
float mathScore;
float englScore;
float physScore;
float chemScore;
float totalScore;
}
同樣,我們的那個排序工具類還是保留,只是不再實現它的排序方法了,如下:
class SortHelper
{
def list
public SortHelper(list)
{
this.list = list
}
}
當然,我們的排序方法是要留給ExpandoMetaClass來實現的,下面就是:
['chinScore','mathScore','englScore','physScore','chemScore','totalScore'].each{
name ->
def name1 = name[0].toUpperCase()+name[1..-1]
SortHelper.metaClass."sortBy${name1}" = {
->
def comparator = {
node1,node2 ->
return node1."${name}".compareTo(node2."${name}")
} as Comparator
Collections.sort(delegate.list,comparator)
}
}
我們的想法就是,把要實現的方法的方法名(或部分)組成一個數組,然後依次遍歷,交給ExpandoMetaClass來添加方法。
上面的代碼都很簡單,在這裡我們就不再多說了。
最後,我們來寫代碼測試上面的添加方法了。如下:
List scores = [new Student(no:'123',name:'Tom',chinScore:90,mathScore:99,englScore:60,physScore:88,chemScore:96)]
scores<<new Student(no:'124',name:'Mike',chinScore:88,mathScore:90,englScore:90,physScore:98,chemScore:87)
scores<<new Student(no:'125',name:'Alice',chinScore:100,mathScore:55,englScore:98,physScore:67,chemScore:56)
def sorter = new SortHelper(scores)
sorter.sortByChinScore()
scores.each{
println it.name
}
運行結果為:
Mike
Tom
Alice
我們都記得我們的初中數學裡學過的對數的概念:對數裡面有一個底的概念,比如我們在程序裡把以2為底的對數寫成如下的樣子:
log2(8)
即以2為底8的對數,這樣,我們可以以任何一個自然數為底,但我們最常用的是以10為底的對數,即:
log10(100)
以10為底的對數,我們通常寫成:
lg(100)
問題為:我們在某種情況下,可能會計算以1到100之間的任何一個數為底的對數。按照上面的分析,其實,就是我們需要給某個工具類100個計算對數的方法。
這就是方法名的動態性的使用了。
我們還是先給出這個工具類來:
class Util
{
}
這個工具類什麼也都沒有實現,留給我們在運行期內動態添加方法,如下:
(1..101).each{
int base->
Util.metaClass.'static'."log$base" = {
int n -> Math.log(n) / Math.log(base)
}
}
它是把1到100進行遍歷,產生了一百個方法,至於如何計算各個底的對數的方法,那是數學公式的問題,我們在這裡不再詳述。
有了上面的代碼,我們現在就可以寫測試代碼了:
println Util.log20(400)
println Util.log100(100)
運行結果為:
2.0
1.0
現在,我們可以看到,我們的這些對數方法是不是寫得很漂亮?
在Groovy語言中,動態委派技術永遠是我們最感興趣的技術。而這種動態委派技術的實現,其實也需要用到我們使用ExpandoMetaClass類來實現的動態方法名。
下面就來說一說如何實現動態委派技術。
這是一個簡單的委派類:
class Delegator {
private targetClass
private delegate
Delegator(targetClass, delegate) {
this.targetClass = targetClass
this.delegate = delegate
}
def delegate(String methodName) {
delegate(methodName, methodName)
}
def delegate(String methodName, String asMethodName) {
targetClass.metaClass."$asMethodName" = delegate.&"$methodName"
}
}
這個類十分的簡單,可以明顯的看到“delegate(String methodName, String asMethodName)”方法就是使用的ExpandoMetaClass類實現的動態方法名,如下:
targetClass.metaClass."$asMethodName" = delegate.&"$methodName"
現在,我們就來使用這個動態委派技術。
下面,我們有一個A類:
public class A{
def a()
{
println 'invoke a function...'
}
}
它就是我們需要委派的原始類。下面是一個簡單的B類,沒有任何的方法:
public class B{
}
下面就可以寫測試代碼了:
def a = new A()
def delegator = new Delegator(B,a)
delegator.delegate 'a'
delegator.delegate 'a','b'
delegator.delegate 'a','c'
def b = new B()
b.a()
b.b()
b.c()
在上面的代碼中,我們分別把A類的“a”方法委派給了B類的三個方法:“a”、“b”和“c”方法。
最後,我們在B類對象中調用這三個方法。運行結果為:
invoke a function...
invoke a function...
invoke a function...
這充分的體現了使用的ExpandoMetaClass類實現的方法名的動態性的特點。
值得注意的是,上面的動態委派技術的實現,需要在我們的Groovy1.5及以上的版本中才能正常編譯。可以確定的是,Groovy1.0版本是不能編譯的。
同時需要說明的是,我的所有文字,除非注明需要Groovy1.5及以上版本,否則都可以在Groovy1.0版本及以上編譯運行。
謝謝!