在物件導向程式設計中,元類別(英語:metaclass)是一種實例是類的類。普通的類別定義的是特定對象的行為,元類別定義的則是特定的類及其實例的行為。不是所有物件導向程式語言都支援元類別。在它能做的事情之中,元類別可以覆寫任何給定方面類行為的程度是不同的。元類別可以通過使類成為頭等對象來實現,在這種情況下元類別簡單的就是構造類的一個對象。每個語言都有它自己的元對象協定,給出對象、類和元類別如何互動的規則[1]。
Smalltalk-80元類別
在Smalltalk中,所有東西都是對象。此外,Smalltalk是類別為基的系統,這意味着所有對象都有一個類,它定義這個對象的結構(比如說這個類擁有實例變數),和這個對象所理解的訊息。二者在一起蘊含了,在Smalltalk中,類是一個對象,因此類也需要是它的元類別的實例。[2]
元類別在Smalltalk-80系統中的主要角色,是提供協定來初始化類別變數,和建立元類別的唯一實例(也就是其對應的類)的初始化實例。
為了允許類擁有它們自己的方法,和叫作類別實例變數它們自己的實例變數,Smalltalk-80為每個類C
介入了它們自己的元類別C class
。就像實例方法實際上屬於類一樣,類別方法實際上屬於元類別。在類中定義實例變數和類別變數,而在元類別中定義類別實例變數。
每個元類別在效果上都是單例類。就像連體雙胞胎,類和元類別是共生的。元類別有一個實例變數thisClass
,它指向它結合的類。平常的Smalltalk類瀏覽器,不將元類別展示為單獨的類,轉而允許一起同時編輯類和它的元類別。
要得到一個實例的類,需要向它傳送訊息呼叫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
、或Widget
或Car
等這樣的東西,除了Object
之外,所有的類都有一個超類。元類別所繼承的元類別,就是元類別對應的類所繼承的類的元類別。
在一個訊息被傳送到對象的時候,方法的尋找開始於它的類。如果沒有找到則在上行超類鏈,停止於Object
而不管找到與否。在一個訊息被傳送到一個類的時候,類別方法尋找開始於它的元類別,並上行超類鏈至Object class
。直至Object class
,元類別的超類層級並列於類的超類層級。在Smalltalk-80中,Object class
是Class
的子類:
Object class superclass == Class.
類別方法的尋找在元類別鏈之後仍可繼續下去,所有元類別都是Class
的在繼承層級中的子類,它是所有元類別的抽象超類,它描述這些類的一般性質,繼而最終可上溯至Object
。
四個類提供描述新類的設施,下面是它們的繼承層級(起自Object
),和它們提供的主要設施:
Object
,對象類是所有類的基礎類,它為所有對象提供公共的方法,即公共的預設行為。至少包括了:測試對象的功能比如class
方法,比較對象,對象複製,訪問對象的各部份,列印和儲存對象,錯誤處理。Behavior
,行為類別定義了擁有實例的對象所需要的最小狀態,它提供建立一個類別的實例的new
方法。特別是,它定義了Smalltalk-80直譯器所用到的狀態,並為編譯方法原始碼提供到編譯器的基本介面,如compile:
等方法。Behavior
描述的這個狀態,包括了一個類層級連接(superclass:
),一個方法字典(methodDictionary:
、addSelector:withMethod:
),和對實例的描述(依據數目和對它們的變數的表示)。儘管一個類的多數設施都規定在Behavior
中,但很多訊息不能於此實現,對類的完全描述轉而在它的子類之中提供。ClassDescription
,類描述類為Class
和Metactass
提供了共同的超類。它表現類命名(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
(對象類)。 - 每個類,包括
Class
和Metaclass
,在尋找尋找方法時,首先尋找自己的元類別;然後按元類別繼承鏈上溯,最終經過Object class
(對象元類別)而上至Class
;接着按類繼承鏈上溯,不經過與其並列的Metaclass
,最終上至Object
。 - 每個元類別,包括
Class class
和Metaclass class
,在尋找方法時,因為都是Metaclass
的實例,所以首先尋找Metaclass
;然後按類繼承鏈上溯,不經過與其並列的Class
,最終上至Object
。
下面是兩個示意圖,二者都是縱向連線表示實例聯絡,而橫向連線表示繼承聯絡。實例聯絡以Metaclass
(元類別類)及其元類別為頂端,而繼承聯絡以Object
(對象類)及其元類別為中心,其中Object class
(對象元類別)繼承Class
(類類)是串接元類別繼承鏈與類繼承鏈的關鍵環節。前者圖示採用Smalltalk-80藍皮書的樣式(但旋轉了180°),將Metaclass
及其元類別放置在最上方的獨立兩行,使得實例聯絡儘量成為樹狀向上匯聚;後者圖示將Metaclass
及其元類別放置在最左邊,使得繼承聯絡儘量都在同一行之上。
-
Smalltalk中在類和元類別之間的繼承和實例聯絡的示意圖,這裏從左至右,第一列是Metaclass元類別和Metaclass(元類別類),第二列是Class元類別和Class(類類),第三列是ClassDescription元類別與Behavior元類別、和ClassDescription(類描述類)與Behavior(行為類),第四列是Object元類別、Object(對象類)和Object實例,第五列是Foo元類別、Foo類和Foo實例,第六列是Bar元類別、Bar類和Bar實例。
下列例子展示,從Smalltalk-80衍生的Squeak和Pharo的樣例代碼的結構[4],它們的繼承層級的根類實際上是ProtoObject
,ProtoObject
封裝了所有對象都必須擁有的極小化的訊息集合,它被設計為引發儘可能多的錯誤,用來支援代理(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.
|
這個結構由兩個部份構成,用戶部份有四個顯式的對象和類及其兩個隱式的元類別:終端對象u
和v
,它們連接到的類A
和B
,它們兩個連接到的右側灰色節點表示的隱式的元類別,其他的對象都是內建部份。
Objective-C元類別
在Objective-C中的元類別,幾乎同於Smalltalk-80的元類別,這是因為Objective-C從Smalltalk引進了很多東西。就像Smalltalk,在Objective-C中實例變數和方法是對象的類別定義的。類也是對象,因此它是元類別的一個實例。
-
在Objective-C中在類和元類別之間的繼承和實例聯絡的示意圖。注意Objective-C有多個根類,每個根類都有獨立的層級。這個示意圖只展示了例子根類NSObject的層級。每個其他根類都有類似的層級。
就像Smalltalk,在Objective-C中類別方法,簡單的是在類對象上呼叫的方法,因此一個類的類別方法,必須定義為在它的元類別中的實例方法。因為不同的類有不同的類別方法集合,每個類都必須有它自己單獨的元類別。類和元類別總是成對建立:執行時系統擁有函數objc_allocateClassPair()
和objc_registerClassPair()
來分別的建立和註冊類-元類別對。
元類別沒有名字,但是到任何類對象的指標,可以通過泛化類型Class
來提及(類似於用作到任何對象的指標的類型id
)。
元類別都是相同的類即根類元類別的實例,而根類元類別是自身的實例。因為類別方法是通過繼承聯絡來繼承的,就像Smalltalk,除了根類元類別之外,元類別繼承聯絡必須並列於類繼承聯絡(比如說如果類A的父類別是類B,則A的元類別的父類別是B的元類別)。
不同於Smalltalk,根類元類別繼承自根類自身(通常為使用Cocoa框架的NSObject
)。這確保了所有的元類別最終都是根類的子類,從而人們可以將根類別的實例方法,它們通常是針對對象有用的實用方法,使用於類對象自身上。
Python元類別
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的隱含的元類別和Ruby類的特徵類之間的對應。Ruby的特徵類模型,使得隱式元類別概念完全統一:所有對象x
,都有它自己的元對象,它叫作x
的特徵類,它比x
高一個元層級。高階特徵類通常是純粹概念上的存在,在大多數Ruby程式中,它們不包含任何方法也不儲存任何(其他)數據[9]。
下面的示意圖展示Ruby樣例代碼的核心結構[10]。這裏的灰色節點表示打開A
和v
特徵類後擴張出來的特徵類。
r = BasicObject
c = Class
class A; end
class B < A; end
u = B.new
v = B.new
class << A; end
class << v; end
|
在語言和工具中的支援
下面是支援元類別的一些最顯著的程式語言。
- Common Lisp,通過CLOS
- Delphi和受它影響的其他Object Pascal版本
- Groovy
- Objective-C
- Python
- Perl,通過元類別pragma,還有Moose
- Ruby
- Smalltalk
- C++(規劃用於C++23)[11]
一些不甚廣泛傳播的語言支援元類別,包括OpenJava、OpenC++、OpenAda、CorbaScript、ObjVLisp、Object-Z、MODEL-K、XOTcl和MELDC。其中幾種語言可追溯日期至1990年代早期並具有學術價值[12]。
Logtalk是Prolog的物件導向擴充,它也支援元類別。
另見
參照
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.