元類別

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

## Smalltalk-80 元類別

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

元類別在 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` 訊息。在 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` 的子類：

類別方法的尋找在元類別鏈之後仍可繼續下去，所有元類別都是 `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-80 衍生的 Squeak 和 Pharo 的樣例代碼的結構，它們的繼承層級的根類實際上是 `ProtoObject`，`ProtoObject` 封裝了所有物件都必須擁有的極小化的訊息集合，它被設計為引發儘可能多的錯誤，用來支援代理（proxy）定義。例如 Smalltalk-80 的 `Object` 中，錯誤處理訊息 `doesNotUnderstand:`，和系統原始訊息 `become:`，就轉而在 `ProtoObject` 中定義了。

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

這個結構由兩個部份構成，使用者部份有四個顯式的物件和類及其兩個隱式的元類別：終端物件 `u` 和 `v`，它們連接到的類 `A` 和 `B`，它們兩個連接到的右側灰色節點表示的隱式的元類別，其他的物件都是內建部份。

## 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` 是元類別。

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

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

### 例子

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

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

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

## Ruby 元類別

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

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

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

圖示還展示了 Ruby 中特徵類的惰性求值。`v` 物件可以有它的特徵類，作為向 `v` 增加「單例方法」的結果而被求值（被分配）。

## 在語言和工具中的支援

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

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

Logtalk 是 Prolog 的物件導向擴充，它也支援元類別。

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

## 另見

* 元程式設計
* 元物件
* 反射式編程

## 參照