國內很多的文章都在說Groovy語言的Mixin機制就是Groovy語言的Categories機制。其實,在外面的Blog上,大量有人在討論Groovy語言應該如何實現它自己的Mixin機制,這就是說明Groovy語言的Mixin機制還沒有定型,處在討論之中。Categories機制當然也能實現部分的Mixin功能,就像Java語言的接口機制,還有組合等等,都能實現部分的Mixin功能。就像C++語言的多繼承一樣,這些"古老"的機制都能或多或少的實現部分的Mixin功能。
其實,Mixin機制也沒有想象的那麼神秘和復雜。從字面上來看,它是由"mix"和"in"兩個單詞組成,它們各自的意義是"混合"和"進來",翻譯過來就是把別的類的功能"混合進來"的意思。從這個意義上講,上面所講到的所有技術,如"多繼承"、"接口"、"組合"和"Categories"都能實現這個機制的部分功能,也就是編譯期的Mixin功能。
但是,現在的Mixin機制所強調的不僅是能夠混入進別的類的功能,更強調的是要在運行期內能夠混入別的類的功能。這就是那些"古老"技術所不能解決的。
在討論中的Groovy語言的Mixin功能,不但要實現編譯期的Mixin功能,同時也要實現運行期的Mixin功能。
雖然Groovy語言的Mixin機制還沒有定型,但由於Groovy語言對MOP的良好支持,所以,我們還是可以比較方便的使用Groovy語言的MOP機制來實現運行期的Mixin功能的。
任何問題的討論都是從一個簡單的例子開始的。
比如,我們現在有一個Window類,它裡面有一個簡單的方法,就是能夠實現開窗的動作。如下:
class Window
{
def open()
{
println 'the window is opened!'
}
}
同時,我們還有一個Human類,這個類可能會很復雜,但為了簡單起見,我們現在不打算實現它的很多功能,只是簡單的表示有這麼一個類。如下:
class Human {
String name
}
現在,我們要操作的當然是Human的實例。本來這兩個類是互不相關的,但現在Human的對象有了一個現實的要求,就是它需要能夠實現"開窗"的動作。
當然,我們可以為Human類實現一個"openWindow"方法,但這一看就不符合面向對象設計的基本原則--類的功能要單一。
接下來的考慮是我們可以在Human類裡使用Window類的"open"方法,這就要使用組合模式,也就實現了編譯期的Mixin機制。但這種編譯期的Mixin機制顯然也太剛性了,可能我們在使用Human類的大多數場合都不會用到"openWindow"方法,卻把它組合到Human類裡。
最後,我們的考慮就是運行期的Mixin機制,這種實現肯定既實現了Mixin的功能,又拋棄了編譯期的Mixin機制的弱點。
現在,我們就來實現運行期的Mixin機制,當然是借助於ExpandoMetaClass類的強大功能。
首先,我們來獲取Window對象:
def window = new Window()
接著,我們要在運行期內借用該對象的"open"方法:
Human.metaClass.openWindow = window.&"open"
現在,我們就可以測試了:
def human = new Human()
human.openWindow()
運行的結果為:
the window is opened!
這就完成了一個簡單的運行期的Mixin功能。
現在,我們的需求可能會在這個基礎上進一步擴展,比如,有一個機器人也希望擁有開窗的功能,或者有一個自動裝置也能開窗。這就需要把我們的Window類的"open"功能Mixin到不同的類中去。
當然,我們可以使用上面的方法,對每一個需要使用開窗功能的類使用ExpandoMetaClass類一一實現。
但既然有這麼多類希望實現相同的功能,我們當然希望有一個工具來幫助我們簡化我們的工作。
class Mixin {
private targetClass
def Mixin(targetClass)
{
this.targetClass = targetClass
}
def mixinWith(String asMethodName, Closure closure)
{
targetClass.metaClass."$asMethodName" = closure
}
}
有了這個Mixin類,我們就可以這樣實現Human類的開窗功能:
def window = new Window()
def m = new Mixin(Human)
m.mixinWith("openWindow",window.&open)
def human = new Human()
human.openWindow()
這個Mixin幫忙類的功能就比較強大了,它可以把其他類的某個方法mixin到Human類去裡;也可以把Window類的"open"方法mixin到其他的某個類中去。
甚至,我們可以直接會Human類添加一個方法,如:
def m = new Mixin(Human)
m.mixinWith('driveCar')
{
println 'The car is running'
}
def human = new Human()
human.driveCar()
運行結果為:
The car is running
當然,你可能對Mixin類的"mixinWith"方法不滿意,希望使用更加DSL的方法,那麼我們就可以這樣實現:
class Mixin {
private targetClass
def Mixin(targetClass)
{
this.targetClass = targetClass
}
def invokeMethod(String name,args)
{
if(name.startsWith('mixinAs'))
{
def methodName = name[7].toLowerCase()+name[8..-1]
targetClass.metaClass."$methodName" = args[0]
}
}
}
然後,我們就可以這樣使用Mixin類:
def window = new Window()
def m = new Mixin(Human)
m.mixinAsOpenWindow window.&open
def human = new Human()
human.openWindow()
上面的代碼行中,形如"m.mixinAsOpenWindow window.&open"就更有可讀性。運行結果同樣為:
the window is opened!