天天看點

什麼是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),下面這個圖形象展示了什麼時菱形繼承:

  

什麼是Mixin模式:帶實作的協定

  從上圖可以看到,類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:

wiki: Mixin

wiki: Duck Typing

本文版權歸作者xybaby(博文位址:http://www.cnblogs.com/xybaby/)所有,歡迎轉載和商用,請在文章頁面明顯位置給出原文連結并保留此段聲明,否則保留追究法律責任的權利,其他事項,可留言咨詢。

繼續閱讀