oo::define / oo::objdefine — 클래스와 객체 정의·구성하기

oo::define / oo::objdefine — 클래스와 객체 정의·구성하기

객체지향 프로그래밍을 하려면 클래스의 구조를 정의하고, 그 뒤에도 메서드를 추가하거나 슬롯을 조정할 수 있어야 해요. TclOO에서 oo::define은 클래스의 구성을, oo::objdefine은 객체의 구성을 담당해요. 둘은 같은 형식으로 동작하니 하나를 익히면 나머지도 쉽게 쓸 수 있어요.

출처: Tcl 공식 문서 — define

본문

시그니처

package require TclOO
oo::define class defScript
oo::define class subcommand arg ?arg ...?
oo::objdefine object defScript
oo::objdefine object subcommand arg ?arg ...?
oo::Slot arg...

클래스 계층

oo::object
 → oo::Slot

설명

oo::define 명령은 클래스의 구성을 제어하고, oo::objdefine 명령은 객체(클래스도 인스턴스 객체로 포함)의 구성을 제어해요. 구성은 classobject 인자가 가리키는 개체에 적용돼요. 클래스를 구성하면 그 클래스의 모든 서브클래스와, 그 클래스의 인스턴스이거나 그것을 믹스인(mixin)하는 모든 객체의 구성도 함께 갱신돼요(개별 인스턴스 구성으로 수정된 경우 제외). 구성 방식은 defScript 인자나 subcommand와 뒤따르는 arg 인자들로 제어돼요. 후자가 있으면 subcommand부터의 모든 인자를 목록으로 만들어 그 목록을 defScript 인자로 쓴 것과 정확히 같아요.

클래스 구성

oo::definedefScript에서 지원하는 명령들은 다음과 같아요. 각각은 subcommand 형태로도 쓸 수 있어요.

constructor argList bodyScript

클래스의 생성자를 만들거나 갱신해요. 생성자의 형식 인자(argList, Tcl proc 명령과 같은 형식)와 본문(bodyScript)을 지정해요. 생성자 본문이 평가될 때의 현재 네임스페이스는, 생성되는 객체에 고유한 네임스페이스예요. 생성자 안에서는 next 명령으로 슈퍼클래스의 생성자를 호출해야 해요. bodyScript가 빈 문자열이면 생성자는 삭제돼요.

deletemethod name ?name ...?

클래스에서 name 메서드를 각각 삭제해요. 그 메서드는 이전에 그 클래스에 존재해야 해요. 클래스의 슈퍼클래스에는 영향이 없고, 서브클래스·인스턴스(수정 중인 클래스를 통한 호출 체인을 가진 경우 제외)나 클래스 객체 자체에도 영향을 끼치지 않아요.

destructor bodyScript

클래스의 소멸자를 만들거나 갱신해요. 소멸자는 인자를 받지 않으며 본문은 bodyScript예요. 객체가 삭제될 때 소멸자가 호출되고, 그때 객체의 고유 네임스페이스가 현재 네임스페이스가 돼요. 소멸자는 next 명령으로 슈퍼클래스의 소멸자를 호출해야 해요. 소멸자가 호출되지 않는 상황(예: 인터프리터가 파괴되는 경우)도 있음에 주의하세요. bodyScript가 빈 문자열이면 소멸자는 삭제돼요.

소멸자 평가 중의 에러는 객체 파괴를 일으킨 코드로 반환되지 않아요. 대신 현재 정의된 bgerror 핸들러로 전달돼요.

export name ?name ...?

지정한 각 메서드 name이 (인스턴스 객체의 명령을 통해) 인스턴스 밖에서도 쓰일 수 있도록 export되게 해요. 메서드 자체는 슈퍼클래스가 정의할 수도 있어요. 서브클래스의 export가 슈퍼클래스의 가시성을 덮고, 그것은 다시 인스턴스가 덮을 수 있어요.

filter ?-slotOperation? ?methodName ...?

이 슬롯(아래 SLOTTED DEFINITIONS 참고)은 클래스 인스턴스에 대한 메서드 호출이 허용될지, 또 그 메서드 결과가 무엇인지를 가드하는 데 쓰는 메서드 이름 목록을 설정·갱신해요. 각 methodName은 필터 메서드 하나를 가리켜요(노출 여부 무관). 서브클래스가 정의할 수 있으므로 존재하지 않는 메서드를 지정해도 에러가 아니에요. 기본적으로 이 슬롯은 추가(append) 방식으로 동작해요.

forward name cmdName ?arg ...?

name이라는 포워드 메서드를 만들거나 갱신해요. 그 메서드는 호출자가 메서드에 준 인자들 앞에 arg 등을 덧붙여 cmdName 명령으로 전달되도록 정의돼요. cmdName은 항상 호출 객체의 네임스페이스 규칙으로 해석돼요 — 완전히 한정되지 않으면 각 객체의 네임스페이스 경로를 따라, 또는 전역 네임스페이스에서 찾아져요. name이 소문자로 시작하면 메서드는 export되고, 아니면 export되지 않아요.

method name argList bodyScript

프로시저-류 스크립트로 구현된 메서드를 만들거나 갱신해요. 메서드 이름은 name, 형식 인자는 argList(Tcl proc 형식), 본문은 bodyScript예요. 메서드 본문이 평가될 때의 현재 네임스페이스는 현재 객체에 고유한 네임스페이스예요. name이 소문자로 시작하면 export되고 아니면 아니며, export·unexport로 덮을 수 있어요.

mixin ?-slotOperation? ?className ...?

이 슬롯은 정의되는 클래스의 모든 인스턴스에 믹스인될 추가 클래스 목록을 설정·갱신해요. 각 className은 믹스인할 클래스 하나를 가리켜요. 기본적으로 이 슬롯은 교체(replacement) 방식으로 동작해요.

renamemethod fromName toName

클래스 안의 fromName 메서드를 toName으로 이름을 바꿔요. fromName은 이전에 클래스에 존재해야 하고, toName은 그 클래스의 기존 메서드를 가리키면 안 돼요. 슈퍼클래스·서브클래스·인스턴스(수정 중인 클래스를 통한 호출 체인 제외)·클래스 객체에는 영향이 없어요. 메서드의 export 상태는 바뀌지 않아요.

self subcommand arg ...
self script

이 명령은 정의되는 클래스에 대해 oo::objdefine을 호출하는 것과 같아요(아래 CONFIGURING OBJECTS 참고). oo::define·oo::objdefine과 같은 인자 처리 패턴을 따르며, "oo::define cls self subcommand ...""oo::objdefine cls subcommand ..."와 동일하게 동작해요.

superclass ?-slotOperation? ?className ...?

이 슬롯은 정의되는 클래스의 슈퍼클래스를 바꾸게 해 줘요. 각 className은 정의 클래스의 슈퍼클래스가 될 클래스 하나를 가리켜요. 객체를 클래스에서 비클래스로(또는 그 반대로) 바꾸면 안 되고, 빈 부모 클래스는 oo::object와 동등하며, oo::object·oo::class의 부모 클래스는 수정할 수 없어요. 기본적으로 이 슬롯은 교체 방식으로 동작해요.

unexport name ?name ...?

지정한 각 메서드 name이 export되지 않게 해요(즉 인스턴스 밖에서 객체 명령으로는 쓸 수 없고, 각 객체 컨텍스트에 보이는 my 명령으로만 쓸 수 있어요). 메서드 자체는 슈퍼클래스가 정의할 수도 있어요. 서브클래스의 unexport가 슈퍼클래스 가시성을 덮고, 인스턴스 unexport가 다시 덮을 수 있어요.

variable ?-slotOperation? ?name ...?

이 슬롯은 지정한 각 변수가 정의되는 클래스가 선언한 메서드·생성자·소멸자에서 자동으로 사용 가능해지도록 해요. 각 변수 이름에는 네임스페이스 구분자가 없어야 하고 배열 접근처럼 보이면 안 돼요. 모든 변수는 메서드가 실행되는 인스턴스 객체에 실제로 존재하게 돼요. 슈퍼클래스·서브클래스가 선언한 변수 목록은 완전히 분리되어 있고, 인스턴스가 선언한 것도 마찬가지예요. 변수 이름 목록은 이 클래스가 선언한 메서드(·생성자·소멸자)에만 적용돼요. 기본적으로 이 슬롯은 추가 방식으로 동작해요.

객체 구성

oo::objdefinedefScript에서 지원하는 명령들은 다음과 같아요. 각각은 subcommand 형태로도 쓸 수 있어요.

class className

객체의 클래스를 생성 후에 바꾸게 해 줘요. 이때 클래스의 생성자는 호출되지 않으므로, 추가 구성 작업을 하지 않으면 객체가 불일치 상태일 수 있음에 주의하세요.

deletemethod name ?name ...?

객체에서 name 메서드를 각각 삭제해요. 그 메서드는 이전에 그 객체에 존재해야 해요(예: oo::objdefine method로 만들어진 것). 객체가 인스턴스로 있는 클래스에는 영향이 없고, 그 클래스가 제공하는 메서드의 노출도 제거하지 않아요.

export name ?name ...?

지정한 각 메서드 name이 객체 밖에서 객체 명령으로 쓰일 수 있게 export되게 해요. 메서드 자체는 클래스·슈퍼클래스가 정의할 수도 있어요. 객체 export가 클래스 가시성을 덮어요.

filter ?-slotOperation? ?methodName ...?

이 슬롯은 객체에 대한 메서드 호출이 허용될지와 그 결과가 무엇인지를 가드하는 메서드 이름 목록을 설정·갱신해요. 각 methodName은 필터 메서드 하나를 가리켜요. 존재하지 않는 메서드를 지정해도 에러가 아니에요. 실제 필터 목록은 객체가 인스턴스로 있는 클래스들에 설정된 필터에도 의존해요. 기본적으로 이 슬롯은 추가 방식으로 동작해요.

forward name cmdName ?arg ...?

name이라는 포워드 객체 메서드를 만들거나 갱신해요. 메서드는 호출자가 준 인자들 앞에 arg 등을 덧붙여 cmdName 명령으로 전달되도록 정의돼요. 포워드 메서드는 method subcommand로 삭제해야 해요. name이 소문자로 시작하면 export되고 아니면 아니에요.

method name argList bodyScript

객체 메서드를 만들거나 갱신하거나 삭제해요. 메서드 이름은 name, 형식 인자는 argList(Tcl proc 형식), 본문은 bodyScript예요. 본문이 평가될 때의 현재 네임스페이스는 객체에 고유한 네임스페이스예요. name이 소문자로 시작하면 export되고 아니면 아니에요.

mixin ?-slotOperation? ?className ...?

이 슬롯은 객체에 믹스인될 추가 클래스의 개별 목록을 설정·갱신해요. 각 className은 믹스인할 클래스 하나를 가리켜요. 기본적으로 이 슬롯은 교체 방식으로 동작해요.

renamemethod fromName toName

객체 안의 fromName 메서드를 toName으로 이름을 바꿔요. fromName은 이전에 객체에 존재해야 하고, toName은 그 객체의 기존 메서드를 가리키면 안 돼요. 객체가 인스턴스로 있는 클래스에는 영향이 없고, 클래스가 제공하는 메서드를 인스턴스 객체에서 이름 변경할 수 없어요(oo::objdefine 포워드 메서드가 비슷한 능력을 줄 수는 있어요). 메서드의 export 상태는 바뀌지 않아요.

unexport name ?name ...?

지정한 각 메서드 name이 export되지 않게 해요(즉 객체 밖에서 객체 명령으로는 쓸 수 없고, 객체 컨텍스트의 my 명령으로만 쓸 수 있어요). 메서드 자체는 클래스가 정의할 수도 있어요. 인스턴스 unexport가 클래스 가시성을 덮어요.

variable ?-slotOperation? ?name ...?

이 슬롯은 지정한 각 변수가 정의되는 객체가 선언한 메서드에서 자동으로 사용 가능해지도록 해요. 각 변수 이름에는 네임스페이스 구분자가 없어야 하고 배열 접근처럼 보이면 안 돼요. 모든 변수는 메서드가 실행되는 객체에 실제 존재하게 돼요. 객체가 인스턴스로 있는 클래스·믹스인이 선언한 변수 목록은 완전히 분리되어 있어요. 기본적으로 이 슬롯은 추가 방식으로 동작해요.

슬롯 정의 (Slotted Definitions)

클래스나 객체의 구성 가능한 정의 중 일부는 슬롯 정의(slotted definition)예요. 즉 그 구성이 클래스 oo::Slot의 인스턴스인 슬롯 객체로 구현되는데, 이 객체가 슬롯 내용(클래스 이름, 변수 이름 등)을 이루는 값 목록을 관리해요.

oo::Slot 클래스는 슬롯에 대해 할 수 있는 세 연산(메서드)을 정의해요:

slot -append ?member ...?

주어진 member 요소를 슬롯 정의에 추가해요.

slot -clear

슬롯 정의를 빈 목록으로 설정해요.

slot -set ?member ...?

슬롯 정의를 주어진 member 요소로 교체해요.

이 때문에 슬롯의 기본 연산을 쓰면서 첫 member 인자가 하이픈으로 시작하면 에러가 나요. 그런 경우에는 위 연산 중 하나를 명시적으로 써야 해요.

표준 슬롯처럼 동작하는 자신만의 슬롯을 정의하는 경우에만 oo::Slot의 인스턴스를 만들면 돼요.

슬롯 구현

내부적으로 슬롯 객체는 --default-operation 메서드도 정의하고, 이것은 슬롯의 기본 연산으로 포워드돼요(예: 클래스 variable 슬롯의 경우 my -append로 포워드). 그리고 구현 인터페이스를 제공하는 다음 메서드들이 있어요:

slot Get

슬롯의 현재 내용인 목록을 반환해요. 이 메서드는 반드시 oo::define이나 oo::objdefine 호출이 만든 스택 프레임에서 호출해야 해요.

slot Resolve element

슬롯 컬렉션의 요소 하나를 해석된 형태로 변환해요. 단순한 값이면 그냥 값을 반환할 수 있지만, 명령·클래스 참조를 담은 슬롯이면 완전히 한정된 형태로 변환해야 해요(문자열 비교가 가능하도록). namespace which 같은 것으로 포워드하면 돼요.

slot Set elementList

슬롯 내용을 목록 elementList로 설정하고 빈 문자열을 반환해요. 이 메서드는 반드시 oo::define·oo::objdefine 호출이 만든 스택 프레임에서 호출해야 해요.

이 메서드들의 구현은 슬롯마다 다르고(클래스·객체 정의의 올바른 부분에 접근하는 책임), 슬롯에는 이런 요소들을 묶어주는 unknown 메서드 핸들러가 있으며, 자신의 destroy 메서드를 숨겨 의도치 않게 호출되지 않게 해요. 슬롯 메커니즘 자체의 사용자 변경은 이름이 하이픈으로 시작하는 새 연산을 정의하는 것으로 제한하는 게 좋아요.

슬롯 인스턴스는 관리하는 슬롯의 저장소를 담을 것으로 기대되지 않아요. 그 저장소는 관리되는 클래스·객체에 있거나 거기에 붙어 있어요. 그 인스턴스들은 Get·Set 메서드(그리고 선택적으로 Resolve; 기본값은 아무것도 하지 않는 통과)의 자체 구현을 제공해야 해요.

예제

oo::defineoo::objdefine 명령의 두 형태를 모두 사용하는 방법(둘은 같은 방식으로 동작함)과, 그 subcommand 넷을 보여주는 예제예요:

oo::class create c
c create o
oo::define c method foo {} {
 puts "world"
}
oo::objdefine o {
 method bar {} {
 my Foo "hello "
 my foo
 }
 forward Foo ::puts -nonewline
 unexport foo
}
o bar → prints "hello world"
o foo → error "unknown method foo"
o Foo Bar → error "unknown method Foo"
oo::objdefine o renamemethod bar lollipop
o lollipop → prints "hello world"

추가 클래스를 객체에 믹스인하는 예제와, mixin이 추가를 지원하는 슬롯임을 보여주는 예제예요:

oo::object create inst
inst m1 → error "unknown method m1"
inst m2 → error "unknown method m2"

oo::class create A {
 method m1 {} {
 puts "red brick"
 }
}
oo::objdefine inst {
 mixin A
}
inst m1 → prints "red brick"
inst m2 → error "unknown method m2"

oo::class create B {
 method m2 {} {
 puts "blue brick"
 }
}
oo::objdefine inst {
 mixin -append B
}
inst m1 → prints "red brick"
inst m2 → prints "blue brick"

더 알아보기

  • next, oo::class, oo::object
  • 키워드: class, definition, method, object, slot