Mixin(織入)模式並不是GOF的《設計模式》歸納中的一種,但是在各種語言以及框架都會發現該模式(或者思想)的一些應用。簡單來說,Mixin是帶有全部實現或者部分實現的接口,其主要作用是更好的代碼復用。本文將介紹Mixin的應用場景,以及關於繼承、組合、多繼承、接口的一些思考。
相關概念:
前面提到,Mixin是有部分或者全部實現的接口,其主要作用是代碼復用,需要理解這個簡單的描述,需要先理清一些概念。
繼承與組合:
繼承是面向對象的三大特征(封裝、繼承、多態),如果類A繼承自類B,那么我們稱A為子類(派生類),稱B為父類(基類)。什么時候類A才能繼承類B呢,可以說A是B的一種特殊化,英語來說就是
A is a B,或者A is a kind of B。比如狗(dog)和動物(Animal)這兩個抽象,dog is animal,這個是成立的,所以dog可以繼承自animal。
而組合代表的是其中一個類的對象是另一個類的對象的組成組合,英語來說,“
has a”,比如人(People)這個類有一個屬性addr是一個地址類(Address)的實例,就是說每個人都有一個地址。
不管是繼承還是組合,都起到了代碼復用的作用,但又各有優缺點。上面繼承和組合的例子都很明顯,但有些情況就不那么容易區分兩類事物是繼承還是組合的關系了,或者說,兩個類之間既可以用繼承,又可以用組合,比如設計模式中的adapter模式,既可以類適配,又可以組合適配(對象適配)。
多繼承與接口:
在使用編程語言抽象事物之間的繼承關系的時候,需要考慮對多繼承的實現。所謂多繼承就是說一個類有多個基類,舉個簡單的例子,dog是animal,同時dog又是runnable(可以跑動的對象)。多繼承對於人類的思維來說是比較正常直觀的,但是對於計算機編程語言,都會遇到一個繞不過去的問題,那就是菱形繼承(diamond problem),下面這個圖形象展示了什么時菱形繼承:

從上圖可以看到,類B、類C都繼承了類A,類D同時繼承了類B和類C,在繼承關系上就形成了
“菱形”--類D有兩條路徑到達類A。菱形問題帶來什么問題呢,如果類A定義了某個方法foo,而且B和C都沒有重寫該方法,那么B和C都會有某種機制找到foo,那么對於D的實例在調用foo方法的時候,是調用到B中指向的foo方法還是C中指向的foo方法呢?
菱形繼承的問題會影響到語言的設計,一些編程語言支持多繼承,如C++、python等等;另外一些則不支持多繼承,如Java,ruby等。對於支持多繼承的語言,為了解決菱形繼承的問題,一般都會使用特定的方法,比如C++中的虛繼承,python中的新式類的MRO。而對於不支持多繼承的語言,一般使用某種接口(或者約定、協議)來實現多繼承的功能,如Java中的interface,ruby中的include module。
duck typing:
一個事物是不是鴨子(duck),如果它走起來像一只鴨子、叫起來也像一只鴨子,即從表現來看像一只鴨子,那么我們就認為它是一只鴨子。把這種思想應用到編程中,就是duck typing,簡而言之,一個約定要求必須實現某些功能,而某個類實現了這個功能,就可以把這個類當做約定的具體實現來使用。duck typing的思想在編程語言中的使用非常廣泛,如Java中的Interface,Python中的各種protocol,ruby中的include module。
上面提到協議(泛指接口、預定、duck typing,下同)可以用來實現多繼承的功能,在以下情況特別合適:“基類”代表的其實是一種能力或者承諾。比如前面dog同時繼承animal和runnale,但跑(run)就是一種能力的體現,這個時候就可以把runable作為一個協議,dog是可以跑的,所以應該實現run方法,也就遵循了這個協議,那么dog就是一個runable了。從例子可以看出,
如果一個所謂的“基類”事實上代表了某種能力(it can,xxxable)的時候,使用協議比多繼承是更佳合理的選擇。
Mixin:
在前面說清楚了各個概念之后,我們來看看Mixin到表代表了什么,不過再次回顧上面一段提到的java interface和python protocol,這二者本身是沒有任何實現的,都是需要使用者來實現相應的方法。Mixin本身也是一種能力的承諾,但
Mixin不同的不同之處在於Mixin是有部分或者全部實現的,在Mixin中的實現有利於代碼復用。如果是部分實現,那么就是在Mixin中實現整個流程,而實現Mixin約定的類提供關鍵的、該類特有的方法,這有點類似模板模式,也是依賴倒置原則的體現。
不同的語言或者框架中,對Mixin模式有不同的實現形式,python中,除了protocol,也可以用多繼承的形式來實現Mixin,為了區分普通的多繼承,Mixin類的類名一般都會帶上后綴:“Mixin”,比如python lib里面的兩個Mixin類:UserDict.DictMixin和SocketServer.ForkingMixIn。DictMixin類包括部分實現,使用者只要實現幾個核心的函數接口就行了。
Mixin defining all dictionary methods for classes that already have a minimum dictionary interface including getitem, setitem, delitem,and keys.
而python中的SocketServer.ForkingMixIn有全部的實現,所以使用者無需特殊處理,就擁有了fork帶來的好處,例如
1 class ForkingUDPServer(ForkingMixIn, UDPServer): pass 2 class ForkingTCPServer(ForkingMixIn, TCPServer): pass
在python的一些框架中,也有Mixin的身影,如tornado。
在ruby中,並不直接使用Mixin這個單詞,而是使用在類的聲明中include 一個module的辦法,如下面的代碼(來自wiki):
1 class Student 2 include Comparable # The class Student inherits Comparable module using include keyword 3 attr_accessor :name, :score 4 5 def initialize(name, score) 6 @name = name 7 @score = score 8 end 9 10 # Including the Comparison module, requires the implementing class to define the <=> comparison operator 11 # Here's the comparison operator. We compare 2 student instances based on their scores. 12 13 def <=>(other) 14 @score <=> other.score 15 end 16 17 end
首先,include的module叫Comparable (Java中也有一個同名的接口),即可比較的對象,按照之前對協議、約定的講解,是非常適合使用Mixin模式的。其次,ruby中Comparable這個module也是部分實現,需要具體的類實現<=>方法。
總結:
Mixin是一種思想,用部分實現的接口來實現代碼復用。可以用來解決多繼承的問題,又可以用來擴展功能。Mixin在不同的編程語言中又不同的使用形式或者命名,但其本質都是一樣的。
references: