메타클래스

객체 지향 프로그래밍에서 메타클래스는 그 인스턴스가 클래스 자체인 클래스이다. 객체의 동작을 정의하는 일반적인 클래스와 달리, 메타클래스는 클래스 및 해당 인스턴스의 동작을 지정한다. 모든 객체 지향 프로그래밍 언어가 메타클래스 개념을 지원하는 것은 아니다. 지원하는 언어의 경우 메타클래스가 클래스 동작에 대해 가지는 제어의 정도가 다양하다. 메타클래스는 종종 클래스를 일급 객체로 취급하여 구현되며, 메타클래스는 이러한 클래스를 생성하고 관리하는 객체가 된다. 각 프로그래밍 언어는 고유한 메타객체 프로토콜을 따르는데, 이는 객체, 클래스 및 메타클래스 간의 상호 작용을 결정하는 규칙이다. 메타클래스는 코드 생성을 자동화하고 프레임워크 개발을 향상시키기 위해 활용된다.

 파이썬 예시 
파이썬에서 내장 클래스 type은 메타클래스이다. 다음 간단한 파이썬 클래스를 살펴보자.

class Car:
 def __init__(self, make: str, model: str, year: int, color: str):
 self.make = make
 self.model = model
 self.year = year
 self.color = color

 @property
 def description(self) -> str:
 """Return a description of this car."""
 return f"{self.color} {self.make} {self.model}"

실행 시 Car 자체는 type의 인스턴스이다. 위에 보이는 Car 클래스의 소스 코드에는 Car 객체의 바이트 크기, 메모리에서의 이진 레이아웃, 할당 방법, Car가 생성될 때마다 __init__ 메서드가 자동으로 호출되는 등의 세부 사항이 포함되어 있지 않다. 이러한 세부 사항은 새 Car 객체가 생성될 때뿐만 아니라 Car의 어떤 속성에 접근할 때마다 적용된다. 메타클래스가 없는 언어에서는 이러한 세부 사항이 언어 사양에 의해 정의되며 재정의할 수 없다. 파이썬에서는 메타클래스인 type이 Car의 동작에 대한 이러한 세부 사항을 제어한다. type 대신 다른 메타클래스를 사용하여 재정의할 수 있다.

위 예시에는 make, model, year, color의 네 가지 속성과 관련된 일부 중복 코드가 포함되어 있다. 사용자 지정 메타클래스를 사용하여 이러한 중복을 일부 제거할 수 있다. 파이썬에서 메타클래스는 type의 서브클래스로 정의하는 것이 가장 쉽다.
class AttributeInitType(type):
 def __call__(self, *args, **kwargs):
 """Create a new instance."""

 # First, create the object in the normal default way.
 obj = type.__call__(self, *args)

 # Additionally, set attributes on the new object.
 for name, value in kwargs.items():
 setattr(obj, name, value)

 # Return the new object.
 return obj
이 메타클래스는 객체 생성만 재정의한다. 클래스 및 객체 동작의 다른 모든 측면은 여전히 type에 의해 처리된다.

이제 Car 클래스는 이 메타클래스를 사용하도록 다시 작성할 수 있다. 파이썬 3에서는 클래스 정의에 "키워드 인수" metaclass를 제공하여 이를 수행한다.
class Car(object, metaclass=AttributeInitType):
 @property
 def description(self) -> str:
 """Return a description of this car."""
 return " ".join(str(value) for value in self.__dict__.values())

결과 객체 Car는 평소와 같이 인스턴스화할 수 있지만, 원하는 수의 키워드 인수를 포함할 수 있다.
new_car = Car(make='Toyota', model='Prius', year=2005, color='Green', engine='Hybrid')

 스몰토크-80에서 

섬네일|UML 다이어그램으로 표현된 스몰토크-80 메타클래스 계층
섬네일|스몰토크에서 클래스와 메타클래스 간의 상속 및 인스턴스 관계 다이어그램

스몰토크에서는 모든 것이 객체이다. 또한, 스몰토크는 클래스 기반 시스템으로, 모든 객체는 해당 객체의 구조(즉, 객체가 가지는 인스턴스 변수)와 객체가 이해하는 메시지를 정의하는 클래스를 가진다. 이는 스몰토크에서 클래스가 객체이며, 따라서 클래스는 클래스(메타클래스라고 불림)의 인스턴스여야 한다는 것을 의미한다.

예를 들어, 자동차 객체 c는 클래스 Car의 인스턴스이다. 차례로, 클래스 Car는 다시 객체이며, 그 자체로 Car class라고 불리는 Car의 메타클래스의 인스턴스이다. 메타클래스 이름의 공백에 유의하라. 메타클래스의 이름은 평가될 때 메타클래스 객체를 결과로 내놓는 스몰토크 표현식이다. 따라서 Car class를 평가하면 Car의 메타클래스 객체가 생성되며, 그 이름은 Car class이다 (Car class name을 평가하여 Car의 메타클래스 이름을 반환하는 것으로 이를 확인할 수 있다).

클래스 메서드는 인스턴스 메서드가 클래스에 속하는 것처럼 실제로 메타클래스에 속한다. 객체 2에 메시지가 전송될 때, 메서드 검색은 Integer에서 시작된다. 찾을 수 없으면 슈퍼클래스 체인을 따라 올라가서 찾든 못 찾든 Object에서 멈춘다.

Integer에 메시지가 전송될 때 메서드 검색은 Integer class에서 시작되어 슈퍼클래스 체인을 따라 Object class로 진행된다. 지금까지 메타클래스 상속 체인이 클래스 상속 체인을 정확히 따르고 있음에 유의하라. 그러나 Object class가 Class의 서브클래스이므로 메타클래스 체인은 더 확장된다. 모든 메타클래스는 Class의 서브클래스이다.

초기 스몰토크에서는 Class라는 메타클래스가 하나뿐이었다. 이는 모든 클래스가 동일한 메서드, 특히 새 객체를 생성하는 메서드, 즉 new를 가진다는 것을 의미했다. 클래스가 고유한 메서드와 고유한 인스턴스 변수(클래스 인스턴스 변수라고 하며 클래스 변수와 혼동해서는 안 됨)를 가질 수 있도록 스몰토크-80은 각 클래스 C에 대해 고유한 메타클래스 C class를 도입했다. 이는 각 메타클래스가 사실상 싱글턴 클래스임을 의미한다.

메타클래스들이 서로 다르게 동작해야 한다는 요구 사항이 없으므로, 모든 메타클래스는 Metaclass라는 단 하나의 클래스의 인스턴스이다. Metaclass의 메타클래스는 Metaclass class라고 불리며, 이는 다시 클래스 Metaclass의 인스턴스이다.

스몰토크-80에서 모든 클래스(Object 제외)는 슈퍼클래스를 가진다. 모든 메타클래스의 추상 슈퍼클래스는 Class이며, 이는 클래스의 일반적인 특성을 기술한다.

메타클래스에 대한 슈퍼클래스 계층은 클래스에 대한 계층과 평행을 이루지만, Object 클래스는 예외이다. 모든 메타클래스는 Class의 서브클래스이므로:
 Object class superclass == Class.
결합 쌍둥이처럼 클래스와 메타클래스는 함께 탄생한다. Metaclass는 결합된 클래스를 가리키는 인스턴스 변수 thisClass를 가진다.
일반적인 스몰토크 클래스 브라우저는 메타클래스를 별도의 클래스로 표시하지 않는다. 대신 클래스 브라우저는 클래스와 해당 메타클래스를 동시에 편집할 수 있도록 한다.

메타클래스 계층의 클래스 이름은 동일한 이름의 개념과 쉽게 혼동될 수 있다. 예를 들어:
 Object는 모든 객체에 대한 클래스 접근과 같은 공통 메서드를 제공하는 기본 클래스이다. "객체"는 정수, 위젯, Car 등이다.
 Class는 모든 클래스에 대한 공통 메서드를 제공하는 메타클래스의 기반이다(그 자체는 메타클래스가 아님). "클래스"는 Integer, Widget, Car 등과 같은 것이다.
 Metaclass는 모든 메타클래스에 대한 공통 메서드를 제공한다.

네 개의 클래스는 새 클래스를 설명하는 기능을 제공한다. 이들의 상속 계층 (Object에서 시작)과 제공하는 주요 기능은 다음과 같다.

Object - 클래스 접근과 같이 모든 객체에 공통적인 기본 동작
Behavior - 메서드 컴파일 및 객체 생성/실행을 위한 최소 상태
ClassDescription (추상 클래스) - 클래스/변수 이름 지정, 주석
Class - 슈퍼클래스와 유사하고 더 포괄적인 기능
Metaclass - 클래스 변수 초기화, 인스턴스 생성 메시지

 루비에서 
루비는 eigenclass를 도입하고 Metaclass 클래스를 제거하며 클래스-오브 맵을 (재)정의함으로써 스몰토크-80 메타클래스 개념을 순화한다.

변경 사항은 다음과 같이 도식화할 수 있다:

| style="font-size: 200%; vertical-align: middle; padding: 1px 20px;" | →
|

|}
특히 스몰토크의 암시적 메타클래스와 루비의 클래스 eigenclass 간의 대응 관계에 주목하라.
루비 eigenclass 모델은 암시적 메타클래스 개념을 완전히 통일시킨다. 즉, 모든 객체 x는 x의 eigenclass라고 불리는 고유한 메타 객체를 가지며, 이는 x보다 한 단계 더 높은 메타 수준이다. "고차" eigenclass는 일반적으로 순전히 개념적으로 존재한다. 대부분의 루비 프로그램에서는 어떤 메서드도 포함하지 않거나 다른 데이터를 저장하지 않는다.

다음 다이어그램은 스몰토크-80과 루비의 핵심 구조 샘플을 비교하여 보여준다.
두 언어 모두에서 구조는 순환 객체(즉, 파란색 또는 녹색 링크의 조합으로 형성된 주기에서 나타나는 객체)를 포함하는 내장 부분과 클래스 A와 B, 그리고 말단 객체 u와 v의 네 가지 명시적 객체를 포함하는 사용자 부분으로 구성된다.
녹색 링크는 상속의 자식→부모 관계(암시적인 상향 방향 포함)를 보여주고, 파란색 링크는 인스턴스화의 보완적인 멤버→컨테이너 관계를 보여준다(x에서 나가는 파란색 링크는 x에서 메서드가 호출될 때 메서드 조회를 시작하는 x의 최소 실제 컨테이너를 가리킨다). 회색 노드는 eigenclass(스몰토크-80의 경우 암시적 메타클래스)를 나타낸다.

오른쪽 다이어그램은 루비의 eigenclass에 대한 느긋한 계산법 그림도 제공한다. v 객체는 v에 싱글턴 메서드를 추가한 결과로 eigenclass가 평가(할당)될 수 있다.

루비의 class라는 인트로스펙션 메서드에 따르면,
모든 클래스(및 모든 eigenclass)의 클래스는
항상 Class 클래스이다(다이어그램에서 c로 표시됨).
Class와 Struct만이 클래스를 인스턴스로 가지는 클래스이다. Class의 서브클래싱은 허용되지 않는다.

메타클래스의 표준 정의를 따르면 Class와 Struct가 루비의 유일한 메타클래스라고 결론 내릴 수 있다.
이는 스몰토크-80에서 모든 클래스가 자체 메타클래스를 가지므로, 루비와 스몰토크 간의 대응 관계와 모순되는 것처럼 보인다.
이러한 불일치는 루비와 스몰토크의 class 인트로스펙션 메서드 간의 불일치에 기반한다. x ↦ x.class 맵이 말단 객체에서는 일치하지만, 클래스에 대한 제한에서는 다르다. 이미 위에서 언급했듯이, 클래스 x의 경우 루비 표현식 x.class는 항상 Class로 평가된다. 스몰토크-80에서는 x가 클래스이면 x class 표현식은
루비의 x.singleton_class에 해당하며,
이는 x의 eigenclass로 평가된다.

 오브젝티브-C에서 

섬네일|오브젝티브-C에서 클래스와 메타클래스 간의 상속 및 인스턴스 관계 다이어그램. 오브젝티브-C에는 여러 루트 클래스가 있으며, 각 루트 클래스는 별도의 계층 구조를 가진다. 이 다이어그램은 예시 루트 클래스인 NSObject의 계층 구조만 보여준다. 다른 모든 루트 클래스도 유사한 계층 구조를 가질 것이다.

오브젝티브-C의 메타클래스는 스몰토크-80의 메타클래스와 거의 동일하다. 이는 오브젝티브-C가 스몰토크에서 많은 것을 차용했기 때문에 놀라운 일이 아니다. 스몰토크와 마찬가지로 오브젝티브-C에서는 인스턴스 변수와 메서드가 객체의 클래스에 의해 정의된다. 클래스는 객체이므로 메타클래스의 인스턴스이다.

스몰토크와 마찬가지로 오브젝티브-C에서는 클래스 메서드가 단순히 클래스 객체에서 호출되는 메서드이므로, 클래스의 클래스 메서드는 해당 메타클래스에서 인스턴스 메서드로 정의되어야 한다. 다른 클래스는 다른 클래스 메서드 집합을 가질 수 있으므로, 각 클래스는 자체적인 별도의 메타클래스를 가져야 한다. 클래스와 메타클래스는 항상 쌍으로 생성된다. 런타임에는 클래스-메타클래스 쌍을 각각 생성하고 등록하는 함수 objc_allocateClassPair() 및 objc_registerClassPair()가 있다.

메타클래스에 대한 이름은 없지만, 모든 클래스 객체에 대한 포인터는 제네릭 타입 Class로 참조될 수 있다(모든 객체에 대한 포인터에 사용되는 타입 id와 유사).

클래스 메서드는 상속을 통해 상속되므로 스몰토크와 마찬가지로 메타클래스는 클래스 계층과 평행한 상속 스키마를 따라야 한다(예: 클래스 A의 부모 클래스가 클래스 B인 경우, A의 메타클래스 부모 클래스는 B의 메타클래스이다). 루트 클래스는 예외이다.

스몰토크와 달리 루트 클래스의 메타클래스는 루트 클래스(일반적으로 코코아 프레임워크를 사용하는 NSObject) 자체로부터 상속받는다. 이는 모든 클래스 객체가 궁극적으로 루트 클래스의 인스턴스임을 보장하여, 객체 자체에 유용한 유틸리티 메서드인 루트 클래스의 인스턴스 메서드를 클래스 객체에 사용할 수 있도록 한다.

메타클래스 객체는 다르게 동작하지 않으므로(메타클래스에 클래스 메서드를 추가할 수 없으므로 모든 메타클래스 객체는 동일한 메서드를 가짐), 모두 동일한 클래스, 즉 루트 클래스의 메타클래스(스몰토크와 다름)의 인스턴스이다. 따라서 루트 클래스의 메타클래스는 그 자체의 인스턴스이다. 그 이유는 모든 메타클래스가 루트 클래스로부터 상속받으므로 루트 클래스의 클래스 메서드를 상속받아야 하기 때문이다.

 언어 및 도구 지원 
다음은 메타클래스를 지원하는 가장 눈에 띄는 프로그래밍 언어들이다.
 커먼 리스프, CLOS를 통해
 델파이 및 영향을 받은 다른 버전의 오브젝트 파스칼
 그루비
 오브젝티브-C
 ooRexx
 파이썬
 펄, 메타클래스 프라그마 및 Moose를 통해
 루비
 스몰토크
 C++ (표준의 미래 버전에 포함될 가능성 제안)

메타클래스를 지원하는 덜 보편적인 언어로는 OpenJava, OpenC++, OpenAda, CorbaScript, ObjVLisp, Object-Z, MODEL-K, XOTcl, MELDC 등이 있다. 이들 중 몇몇은 1990년대 초반에 만들어졌으며 학술적 관심의 대상이다.

프롤로그의 객체 지향 확장인 Logtalk도 메타클래스를 지원한다.

자원 기술 프레임워크 (RDF)와 통합 모델링 언어 (UML)는 모두 메타클래스를 지원한다.

 같이 보기 

 메타모델
 메타프로그래밍
 메타객체
 종 (유형 이론)
 반영
 동적 처리
 어댑터 패턴
 메타클래스 (시맨틱 웹)

 각주 

 외부 링크 
 메타클래스란 무엇인가?

분류:클래스