物件導向程式設計中,元類別(英語:metaclass)是一種實例是的類。普通的類別定義的是特定對象的行為,元類別定義的則是特定的類及其實例的行為。不是所有物件導向程式語言都支援元類別。在它能做的事情之中,元類別可以覆寫任何給定方面類行為的程度是不同的。元類別可以通過使類成為頭等對象來實現,在這種情況下元類別簡單的就是構造類的一個對象。每個語言都有它自己的元對象協定,給出對象、類和元類別如何互動的規則[1]

Smalltalk-80元類別

Smalltalk中,所有東西都是對象。此外,Smalltalk是類別為基的系統,這意味著所有對象都有一個類,它定義這個對象的結構(比如說這個類擁有實例變數),和這個對象所理解的訊息。二者在一起蘊含了,在Smalltalk中,類是一個對象,因此類也需要是它的元類別的實例。[2]

元類別在Smalltalk-80系統中的主要角色,是提供協定來初始化類別變數,和建立元類別的唯一實例(也就是其對應的類)的初始化實例。

實例聯絡

為了允許類擁有它們自己的方法,和叫作類別實例變數它們自己的實例變數英語Instance variable,Smalltalk-80為每個類C介入了它們自己的元類別C class。就像實例方法實際上屬於類一樣,類別方法實際上屬於元類別。在類中定義實例變數英語Instance variable類別變數英語Class variable,而在元類別中定義類別實例變數。

每個元類別在效果上都是單例類。就像連體雙胞胎,類和元類別是共生的。元類別有一個實例變數thisClass,它指向它結合的類。平常的Smalltalk類瀏覽器英語class browser,不將元類別展示為單獨的類,轉而允許一起同時編輯類和它的元類別。

要得到一個實例的類,需要向它傳送訊息呼叫class方法。類和元類別繼承了其超類的name方法,它返回接收者名字的字串。例如,轎車對象c是類Car的實例,則c class返回Car類對象,而c class name返回'Car';依次類推,Car class返回Car的元類別對象,而Car class name返回依賴於實現,有的是nil,即沒有名字,有的是'Car class',即用空格分隔的類名字和'class'

在早期的Smalltalk-76中,新增類的方式是向Class類傳送new訊息[3]。在Smalltalk-80中,Class是元類別的基礎類,它是類而不是元類別。所有元類別都是一個Metaclass類別的實例。Metaclass類是Metaclass class的實例,而Metaclass class作為元類別,也是Metaclass類別的實例。

繼承聯絡

在Smalltalk-80中,終端對象是一個整數、一個組件、或一台車等,而類是像Integer、或WidgetCar等這樣的東西,除了Object之外,所有的都有一個超類。元類別所繼承的元類別,就是元類別對應的類所繼承的類的元類別。

在一個訊息被傳送到對象的時候,方法的尋找開始於它的類。如果沒有找到則在上行超類鏈,停止於Object而不管找到與否。在一個訊息被傳送到一個類的時候,類別方法尋找開始於它的元類別,並上行超類鏈至Object class。直至Object class,元類別的超類層級並列於類的超類層級。在Smalltalk-80中,Object classClass的子類:

Object class superclass == Class.

類別方法的尋找在元類別鏈之後仍可繼續下去,所有元類別都是Class的在繼承層級中的子類,它是所有元類別的抽象超類,它描述這些類的一般性質,繼而最終可上溯至Object

繼承層級

四個類提供描述新類的設施,下面是它們的繼承層級(起自Object),和它們提供的主要設施:

  • Object,對象類是所有類的基礎類,它為所有對象提供公共的方法,即公共的預設行為。至少包括了:測試對象的功能比如class方法,比較對象,對象複製,訪問對象的各部份,列印和儲存對象,錯誤處理。
    • Behavior,行為類別定義了擁有實例的對象所需要的最小狀態英語State (computer science),它提供建立一個類別的實例的new方法。特別是,它定義了Smalltalk-80直譯器所用到的狀態,並為編譯方法原始碼提供到編譯器的基本介面,如compile:等方法。Behavior描述的這個狀態,包括了一個類層級連接(superclass:),一個方法字典(methodDictionary:addSelector:withMethod:),和對實例的描述(依據數目和對它們的變數的表示)。儘管一個類的多數設施都規定在Behavior中,但很多訊息不能於此實現,對類的完全描述轉而在它的子類之中提供。
      • ClassDescription,類描述類為ClassMetactass提供了共同的超類。它表現類命名(name)、類注釋(comment:)、和命名實例變數(addlnstVarName:)。特別是,它增加了組織在方法字典中方法(compile:classified:)和類自身(category:)的結構。它還提供了在外部串流(檔案)上儲存完全的類描述的機制,和記述對類描述的變更的機制。
        • Class,類類是所有元類別的基礎類,從而為所有類提供公共的方法,它定義了初始化類別變數的initialize方法。Class的實例描述對象的表現和行為,它提供比ClassDescription更具描述性的設施,特別是,它增加了對類別變數名字(addClassVarName:)和共享的池變數(addSharedPool:)的表示。它還提供比Behavior更綜合性的編程支援設施,比如建立一個類的子類的訊息:subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
        • Metaclass,元類別類是建立元類別的類,它為所有元類別提供公共的方法。Metaclass的關鍵性的訊息,是自身初始化訊息,這在GNU Smalltalk中依舊保留;一個是傳送到Metaclass自身的訊息subclassOf: superMeta,用來建立元類別superMeta的一個子類;一個是傳送到Metaclass的一個實例的訊息,用來建立這個元類別的唯一實例,對於建立完全初始化的類,它的每個參數都是需要的:name:environment:subclassOf:instanceVariableNames:shape:classVariableNames:poolDictionaries:category:

方法尋找次序

下面是方法尋找次序的辨析:

  • 每個終端對象,在尋找方法時,都首先尋找自己的類;然後按類繼承鏈上溯,最後不經過Class(類類)和Metaclass(元類別類),最終上至Object(對象類)。
  • 每個類,包括ClassMetaclass,在尋找尋找方法時,首先尋找自己的元類別;然後按元類別繼承鏈上溯,最終經過Object class(對象元類別)而上至Class;接著按類繼承鏈上溯,不經過與其並列的Metaclass,最終上至Object
  • 每個元類別,包括Class classMetaclass class,在尋找方法時,因為都是Metaclass的實例,所以首先尋找Metaclass;然後按類繼承鏈上溯,不經過與其並列的Class,最終上至Object

示意圖

下面是兩個示意圖,二者都是縱向連線表示實例聯絡,而橫向連線表示繼承聯絡。實例聯絡以Metaclass(元類別類)及其元類別為頂端,而繼承聯絡以Object(對象類)及其元類別為中心,其中Object class(對象元類別)繼承Class(類類)是串接元類別繼承鏈與類繼承鏈的關鍵環節。前者圖示採用Smalltalk-80藍皮書的樣式(但旋轉了180°),將Metaclass及其元類別放置在最上方的獨立兩行,使得實例聯絡儘量成為樹狀向上匯聚;後者圖示將Metaclass及其元類別放置在最左邊,使得繼承聯絡儘量都在同一行之上。

例子

下列例子展示,從Smalltalk-80衍生的SqueakPharo的樣例代碼的結構[4],它們的繼承層級的根類實際上是ProtoObjectProtoObject封裝了所有對象都必須擁有的極小化的訊息集合,它被設計為引發儘可能多的錯誤,用來支援代理(proxy)定義[5]。例如Smalltalk-80的Object中,錯誤處理訊息doesNotUnderstand:,和系統原始訊息become:,就轉而在ProtoObject中定義了。

在示意圖中,縱向的綠色連接,展示繼承聯絡的「子→父」關係(隱含的自下而上),橫向的藍色連接展示實例聯絡的「成員→容器」關係,從x出的發藍色連接,指向x的最小實際容器,它是在呼叫在x上的方法時尋找方法的繼承鏈起點:

 
 r := ProtoObject.
 c := Class.
mc := Metaclass.
Object subclass: #A.
A      subclass: #B.
u := B new.
v := B new.
 

這個結構由兩個部份構成,使用者部份有四個顯式的對象和類及其兩個隱式的元類別:終端對象uv,它們連接到的類AB,它們兩個連接到的右側灰色節點表示的隱式的元類別,其他的對象都是內建部份。

Objective-C元類別

Objective-C中的元類別,幾乎同於Smalltalk-80的元類別,這是因為Objective-C從Smalltalk引進了很多東西。就像Smalltalk,在Objective-C中實例變數和方法是對象的類別定義的。類也是對象,因此它是元類別的一個實例。

就像Smalltalk,在Objective-C中類別方法,簡單的是在類對象上呼叫的方法,因此一個類的類別方法,必須定義為在它的元類別中的實例方法。因為不同的類有不同的類別方法集合,每個類都必須有它自己單獨的元類別。類和元類別總是成對建立:執行時系統擁有函式objc_allocateClassPair()objc_registerClassPair()來分別的建立和註冊類-元類別對。

元類別沒有名字,但是到任何類對象的指標,可以通過泛化類型Class來提及(類似於用作到任何對象的指標的類型id)。

元類別都是相同的類即根類元類別的實例,而根類元類別是自身的實例。因為類別方法是通過繼承聯絡來繼承的,就像Smalltalk,除了根類元類別之外,元類別繼承聯絡必須並列於類繼承聯絡(比如說如果類A的父類別是類B,則A的元類別的父類別是B的元類別)。

不同於Smalltalk,根類元類別繼承自根類自身(通常為使用Cocoa框架的NSObject)。這確保了所有的元類別最終都是根類的子類,從而人們可以將根類別的實例方法,它們通常是針對對象有用的實用方法,使用於類對象自身上。

Python元類別

Python中,內建的類type是元類別[6][7]

 
r = object
c = type
class M(c): pass

class A(metaclass=M): pass

class B(A): pass

b = B()
 

類的定義,不包括它的實例對象的細節,如它們的位元組為單位的大小,它們在主記憶體中的二進制格局,它們是如何分配的,每次建立實例時自動呼叫的__init__方法,諸如此類。不只是在建立新實例對象的時候,而且在每次訪問實例對象的任何特性的時候,這些細節都起到作用。在沒有元類別的語言中,這些細節是在語言規定中定義的,並且不能被覆寫(override)。

在Python中,元類別type控制著類行為的這些細節,預設定義出的類自身都是type的實例。新的元類別可以很容易定義為type的子類從而覆寫它,通過向類別定義提供「關鍵字參數」metaclass就可以使用這個新的元類別。

>>> type(b)
<class '__main__.B'>
>>> print(type(B), B.__bases__, [*B.__dict__])
<class '__main__.M'> (<class '__main__.A'>,) ['__module__', '__doc__']
>>> print(type(A), A.__bases__, [*A.__dict__])
<class '__main__.M'> (<class 'object'>,) ['__module__', '__dict__', '__weakref__', '__doc__']
>>> print(type(M), M.__bases__, [*M.__dict__])
<class 'type'> (<class 'type'>,) ['__module__', '__doc__']
>>> print(type(c), c.__bases__)
<class 'type'> (<class 'object'>,)
>>> print(type(r), r.__bases__)
<class 'type'> ()
>>> sorted({*r.__dict__} & {*c.__dict__})
['__delattr__', '__dir__', '__doc__', '__getattribute__', '__init__', '__new__', '__repr__', '__setattr__', '__sizeof__']
>>> sorted({*r.__dict__} - {*c.__dict__})
['__class__', '__eq__', '__format__', '__ge__', '__gt__', '__hash__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__str__', '__subclasshook__']
>>> sorted({*c.__dict__} - {*r.__dict__})
['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__dict__', '__dictoffset__', '__flags__', '__instancecheck__', '__itemsize__', '__module__', '__mro__', '__name__', '__prepare__', '__qualname__', '__subclasscheck__', '__subclasses__', '__text_signature__', '__weakrefoffset__', 'mro']

例子

考慮下面這個最簡單的Python類:

class Car:
    def __init__(self, *args, **kwargs):
        self.__dict__.update(kwargs)
    def __call__(self, **kwargs):
        self.__dict__.update(kwargs)
    @property
    def description(self):
        """返回这辆车的描述."""
        return " ".join(str(value) for value in self.__dict__.values())
>>> new_car = Car(make='Toyota', model='Prius', year=2005, engine='Hybrid')
>>> new_car(color='Green')
>>> new_car.description
'Toyota Prius 2005 Hybrid Green'

上面的例子包含了一些代碼來處理初始化特性,也可以使用元類別來完成這種任務:

class AttributeInitType(type):
    def __new__(*args, **kwargs):
        """返回创建的实例类."""
        cls = type.__new__(*args, **kwargs)
        def call(self, **kwargs):
            self.__dict__.update(kwargs)
        cls.__call__ = call    # 为实例类增加__call__方法
        return cls
    def __call__(cls, *args, **kwargs):
        """返回为实例类创建的实例对象."""
        obj = type.__call__(cls, *args)    # 以正常缺省方式建立实例对象。
        obj.__dict__.update(**kwargs)    # 在这个新对象上设置属性。
        return obj

這個元類別只覆寫實例類和對象建立部份功能。元類別行為的所有其他方面仍由type處理。現在可以重寫類Car使用這個新元類別:

class Car(object, metaclass=AttributeInitType):
    def __init__(self, *args): pass # 接收未预期的位置实际参数
    @property
    def description(self):
        """返回这辆车的描述."""
        return " ".join(str(value) for value in self.__dict__.values())

Ruby元類別

Ruby通過介入其自稱的特徵類(eigenclass),提煉了Smalltalk-80的元類別概念,去除了Metaclass類,並重新定義了class-of對映。變更可以圖示如下[8]

Smalltalk-80
隱式
元類別
終端
對象
Ruby
類的
特徵類
特徵類的
特徵類
終端
對象
終端對象的
特徵類

特別要注意在Smalltalk的隱含的元類別和Ruby類的特徵類之間的對應。Ruby的特徵類模型,使得隱式元類別概念完全統一:所有對象x,都有它自己的元對象,它叫作x的特徵類,它比x高一個元層級。高階特徵類通常是純粹概念上的存在,在大多數Ruby程式中,它們不包含任何方法也不儲存任何(其他)資料[9]

下面的示意圖展示Ruby樣例代碼的核心結構[10]。這裡的灰色節點表示打開Av特徵類後擴張出來的特徵類。

 
r = BasicObject
c = Class
class A; end
class B < A; end
u = B.new
v = B.new

class << A; end
class << v; end
 

圖示還展示了Ruby中特徵類的惰性求值v對象可以有它的特徵類,作為向v增加「單例方法」的結果而被求值(被分配)。

在語言和工具中的支援

下面是支援元類別的一些最顯著的程式語言

一些不甚廣泛傳播的語言支援元類別,包括OpenJava英語OpenJava、OpenC++、OpenAda、CorbaScript英語CorbaScript、ObjVLisp、Object-Z英語Object-Z、MODEL-K、XOTcl英語XOTcl和MELDC。其中幾種語言可追溯日期至1990年代早期並具有學術價值[12]

Logtalk英語LogtalkProlog的物件導向擴充,它也支援元類別。

資源描述框架(RDF)和統一建模語言(UML)二者都支援元類別。

另見

參照

  1. ^ Ira R. Forman and Scott Danforth. Putting Metaclasses to Work. 1999. ISBN 0-201-43305-2. 
  2. ^ Alan Kay. The Early History of Smalltalk. [2022-03-12]. (原始內容存檔於2011-04-29). The most puzzling strange idea – at least to me as a new outsider – was the introduction of metaclasses (really just to make instance initialization a little easier – a very minor improvement over what Smalltalk-76 did quite reasonably already).
    Peter’s 1989 comment is typical and true: 「metaclasses have proven confusing to many users, and perhaps in the balance more confusing than valuable.」 In fact, in their PIE system, Goldstein and Bobrow had already implemented in Smalltalk on 「observer language」, somewhat following the view-oriented approach Ihad been advocating and in some ways like the 「perspectives」 proposed in KRL [Goldstein *].
    Once one can view an instance via multiple perspectives even 「sem-metaclasses」 like Class Class and Class Object are not really necessary since the object-role and instance-of-a-class-role are just different views and it is easy to deal with life-history issues includeding instantiation. This was there for the taking (along with quite a few other good ideas), but it wsn’t adopted. My guess is that Smalltalk had moved into the final phase I memntioned at the beginning of this story, in which a way of doing things finally gets canonized into an inflexible belief structure.
     
  3. ^ Learning Research Group. How To Use the Smalltalk-76 System (PDF) (報告). Xerox Palo Alto Research Center. October 1979 [2022-03-13]. (原始內容 (PDF)存檔於2022-04-12). To define a new class, select a class category in the first pane of the browse window. This selection specifies the category to which the new class will be added, and causes a template to appear in the largest pane of the browse window, the code pane. ……
    The template presented in the code pane looks as follows
        Class new title: ’NameofClass’
        subclassof: Object
        fields: ’names of fields’
        declare: ’names of class variables’
     
  4. ^ The core structure of Smalltalk-80. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始內容存檔於2021-05-06). 
  5. ^ ProtoObject. [2022-01-16]. (原始內容存檔於2022-03-12). 
  6. ^ IBM Metaclass programming in Python, parts 1頁面存檔備份,存於網際網路檔案館), 2頁面存檔備份,存於網際網路檔案館) and 3頁面存檔備份,存於網際網路檔案館
  7. ^ Artima Forum: Metaclasses in Python 3.0 (part 1 of 2)頁面存檔備份,存於網際網路檔案館(part 2 of 2)頁面存檔備份,存於網際網路檔案館
  8. ^ Introduction - Introductory sample. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始內容存檔於2021-05-06). 
  9. ^ Paolo Perrotta. Metaprogramming Ruby 2 (PDF). Pragmatic Bookshelf. 2014 [2022-03-30]. ISBN 978-1-94122-212-6. (原始內容 (PDF)存檔於2022-05-15). 
  10. ^ The core structure of Ruby - Eigenclass actuality. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始內容存檔於2021-05-06). 
  11. ^ Herb Sutter. Metaclasses (PDF). [2020-09-25]. (原始內容存檔 (PDF)於2020-11-11). 
  12. ^ An implementation of mixins in Java using metaclasses (PDF). [2007-11-27]. (原始內容 (PDF)存檔於2007-10-16).