オブジェクト指向 プログラミング においてメタクラス とは、インスタンスがクラス となるクラスのことである。通常のクラスがそのインスタンスの振る舞いを定義するように、メタクラスはそのインスタンスであるクラスを、そして更にそのクラスのインスタンスの振る舞いを定義する。全てのオブジェクト指向プログラミング言語でメタクラスが利用できるわけではない。利用できるものの中でもクラスの振る舞いが定義できる範囲は様々である。各言語はそれぞれ独自のメタオブジェクトプロトコル(MOP)を備えている[ 1] 。メタオブジェクトプロトコルとは、クラスそのものの挙動をもオブジェクト指向のルールで記述し、初期化やインスタンス化のルール、実行状態の管理などをカスタマイズする機構である。Smalltalk 、Common Lisp が代表的である。
Pythonでの例
Python の組み込み(ビルトイン)クラス type
はメタクラスである[ 2] [ 3] [ 4] 。次に示す単純なPythonのクラスについて説明する。
class Car ( object ):
__slots__ = [ 'make' , 'model' , 'year' , 'color' ]
def __init__ ( self , make , model , year , color ):
self . make = make
self . model = model
self . year = year
self . color = color
@property
def description ( self ):
""" このCarの説明を返す """
return " %s %s %s %s " % ( self . color , self . year , self . make , self . model )
このコードを実行した時、Car
は type
のインスタンスになっている。上記の Car
クラスのソースコードには __init__
メソッドが Car
のインスタンスが生成されるたびに呼ばれるといった細々としたことは記述されていない。メタクラスが用意されていない言語ではこのような振る舞いは言語仕様で定義されており、変更することは不可能である。Pythonではメタクラス type
がこれらの動作を決定しており、type
の代わりに違うメタクラスを使用することでこれらの振る舞いを変更することが可能である。
上に示した例は4つの属性 make
と model
、year
、color
の辺りが冗長である。メタクラスを使えば、この冗長さを取り除くことが可能である。Pythonではメタクラスは type
のサブクラスとして定義するのが最も簡単である。
class AttributeInitType ( type ):
def __call__ ( self , * args , ** kwargs ):
""" 新しいインスタンスを生成する """
# 普通にインスタンスを生成する
obj = type . __call__ ( self , * args )
# 生成したインスタンスの属性を設定する
for name in kwargs :
setattr ( obj , name , kwargs [ name ])
# 生成したインスタンスを返す
return obj
このメタクラスはインスタンスの生成を上書きしているだけで、他の機能はまだ type
が処理している。
さて、このメタクラスを用いて Car
クラスを書き直すことが可能である。Python 2ではクラス定義中で __metaclass__
にこのメタクラスを代入すればよい(Python 3では代わりに metaclass=M
と名前付き引数として与える)。
class Car ( object ):
__metaclass__ = AttributeInitType
__slots__ = [ 'make' , 'model' , 'year' , 'color' ]
@property
def description ( self ):
""" Return a description of this car. """
return " %s %s %s %s " % ( self . color , self . year , self . make , self . model )
これで、Car
のインスタンスを次のように生成できる。
cars = [
Car ( make = 'Toyota' , model = 'Prius' , year = 2005 , color = 'green' ),
Car ( make = 'Ford' , model = 'Prefect' , year = 1979 , color = 'blue' )]
Smalltalkにおけるメタクラス
Smalltalk-80でのメタクラスの継承関係を示すUML図。
Smalltalk では全てがオブジェクトである。また、Smalltalkはクラスベースであるため、オブジェクトは必ず、そのオブジェクトの構造(端的にはそのオブジェクトが有するインスタンス変数)や、そのオブジェクトがどんなメッセージを理解するのか(端的にはコールできるメソッド)を定義したいずれかのクラスに属さなければならない。このルールにおいてクラスも例外ではなく、Smalltalkではクラスもオブジェクトであり、同時に別のクラスのインスタンスでもある。このクラスのクラス、つまりあるクラスが属する特殊なクラスが「メタクラス」である。
例えば、「自動車」クラスCar
から生成されたオブジェクトaCar
が、Car
のインスタンスであり、Car
というクラスに属しているのと同じように、クラスCar
自身にも自らが属しているクラス、つまりメタクラスが存在する。原則としてSmalltalkでメタクラスは無名扱いだが、便宜的にクラス名にclass
を付して呼称する慣習がある。Car
なら、そのメタクラスはCar class
である。なおこの表記は(クラスに限らず)あるオブジェクトが属するクラスを第一級オブジェクトとして得るためのSmalltalk式としての意味も併せ持つ(aCar class "=> Car ". (aCar class) class "=> Car class "
)。
クラスメソッド(クラスがコールできるメソッド)は、通常、メタクラスに定義されている。これはインスタンスメソッドがクラスに定義されているのと考え方は同じである。
インスタンスメソッドの場合、たとえば整数の「2」というオブジェクトに何らかのメッセージを送ると、2が属するSmallInteger
というクラスから対応するメソッドがないか探し始める。SmallInteger
に見つからなければそのスーパークラスであるInteger
で…というようにスーパークラスを次々と手繰ってゆき、最後のObject
というルートクラスまで探索を続ける。
クラスメソッドの場合も考え方はほとんど変わらない。たとえばSmallInteger
というクラスに対してメッセージを送ると、メソッド検索はそのクラスであるSmallInteger class
から開始される。そしてインスタンスメソッドの場合と同様に、SmallInteger class
のスーパークラスであるInteger class
、さらにそのスーパークラスを次々と手繰り、Object class
(Object
クラスのメタクラス)まで探索を続ける。なおSmalltalkにおいては、メタクラスの継承関係は原則としてそのインスタンスであるクラスの継承関係と一致する。つまりSmallInteger
のスーパークラスがInteger
なら、そのメタクラス同士も同じ関係、すなわち、SmallInteger class
のスーパークラスはInteger class
である。ただしObject class
だけは例外で、Objectのスーパークラスが未定義(nil)であるのに対し、Object class
のスーパークラスはClass
と定められている。したがって、クラスへのメッセージ送信に伴うメソッド探索はObject class
では終了せず、クラスとしての振る舞いを定めたClass
とそのスーパークラスパスにある二つのクラス(ClassDescription
とBehavior
。後述)を経てObject
に行き着くまで続行される。同時にこのことはSmalltalkにおいてクラスもオブジェクトである、すなわちクラスObject
の(サブ)インスタンスであり、通常のクラスのインスタンス同様Object
に定義されたメソッドをコールできることの理由をうまく説明している。
初期のSmalltalk(Smalltalk-76)には、同じくClass
と名付けられたたったひとつのメタクラスしか用意されていなかった。つまりこのことは、すべてのクラスはClass
のインスタンスであり、他のクラスと同じ共通のメソッドしか持つことができなかった(たとえば、新しいインスタンスを作るためのnew
というメソッドなど)ということを意味する。その後、クラスが独自のメソッド(クラスメソッド)やインスタンス変数(クラスインスタンス変数と呼ばれる。クラスとそのインスタンスで共有できるクラス変数と混同されやすいが別物である)を持てるように、Smalltalk-80では改めてクラスそれぞれが固有のメタクラスが生成されるように拡張され今のかたちになった。
クラスのクラスであるメタクラスは、インスタンスのクラスである通常のクラスほどには独自性を要求されないので、すべてのメタクラスはMetaclass
という名のひとつクラスのインスタンスとして位置づけられている。ちなみにMetaclass
のメタクラスはMetaclass class
だが、同時にこれは前述のルールに則りMetaclass
のインスタンスでもある。このトリックによって、さらにメタ、さらにそのメタ…と永遠に繰り返さずにも済むようになっている。
メタクラスはクラス生成時に同時に生成される。より具体的には、あるクラス(将来のスーパークラス)に対して、そのサブクラスとして例えばFoo
を生成するように命ずると、まず暗黙のうちにFoo class
が生成され、そのインスタンスとしてFoo
が生成される。先にも述べたがメタクラスは無名であり、クラスブラウザのクラス一覧にも現れえない。しかし、メタクラスが必ずクラスとペアで生成されることを利用して、クラスブラウザではclassボタン(古典的なクラスブラウザではクラス一覧を表示する枠に設置されている)を押して表示を切り替えることで対応するメタクラスの定義にアクセスしたりその内容(クラスインスタンス変数やクラスメソッド)を編集可能になっている。
クラスおよびメタクラスの振る舞いは次の4段階の継承階層を経て定義されている。
Object - インスタンス・クラスの別なく、オブジェクトとしての普遍的な振る舞い
Behavior - クラスの最低限の振る舞い(コンパイルされたメソッドの保持やインスタンス生成)
ClassDescription (抽象クラス) - クラスの振る舞い(メソッドのコンパイルやカテゴリ管理)
Class - 通常のクラスの振る舞い(スーパークラスとしての振る舞いやクラス分類など)
Metaclass - メタクラス独自の振る舞い(一部メッセージをクラスにリダイレクトするなど)
Rubyにおけるメタクラス
Rubyでは、クラス自体がClassというクラスのインスタンスであり、Class.new
という形でクラスを動的に生成することもできる[ 5] 。ClassはModuleを継承しており、動的なメソッドの操作といったクラスに共通な機能があるほか、特異メソッド(あるオブジェクトだけに有効なメソッド)を使うことで、Smalltalk-76のような単一のメタクラスでクラスごとの多様性をもたせることができる。
具体的には、存在しないメソッドの呼び出しや、継承・メソッドの追加といったタイミングで呼び出されるフック関数を特異メソッドとして実装することができる[ 6] 。
利用可能な言語とツール
有名なプログラミング言語 でメタクラスが利用可能なものは以下である。
次のようにそれほど普及していない言語の中にはメタクラスが利用できるものもある。いくつかは研究目的であり、1990年代初頭にまで遡る[ 7] : OpenJava やOpenC++ 、OpenAda 、CorbaScript 、ObjVLisp 、Object-Z 、MODEL-K 、XOTcl 、MELDC など。
また、Prolog のオブジェクト指向拡張であるLogtalk でもメタクラスが利用できる。
さらに、Resource Description Framework (RDF)とUnified Modeling Language (UML)は両者共にメタクラスが利用できる。
関連項目
出典
参考文献