什麼是Mixin模式:帶實現的協議

  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也是部分實現,須要具體的類實現<=>方法。java

 


總結:

  Mixin是一種思想,用部分實現的接口來實現代碼複用。能夠用來解決多繼承的問題,又能夠用來擴展功能。Mixin在不一樣的編程語言中又不一樣的使用形式或者命名,但其本質都是同樣的。
 
references:
相關文章
相關標籤/搜索