next, nextto — 슈퍼클래스 메서드 구현 호출하기

next, nextto — 슈퍼클래스 메서드 구현 호출하기

서브클래스에서 메서드를 오버라이드할 때, 부모 클래스가 원래 하던 일을 그대로 이어받고 싶다면 어떻게 하죠? 그때 쓰는 명령이 next예요. 현재 메서드에 의해 덮어쓴 (슈퍼)클래스·믹스인의 메서드 구현을 호출해서, 메서드 체인을 따라가게 해 줍니다.

출처: Tcl Commands — next, nextto

본문

package require TclOO

next ?arg ...?
nextto class ?arg ...?

next 명령은 현재 메서드에 의해 오버라이드된 클래스·슈퍼클래스·믹스인의 메서드 구현을 호출하는 데 사용해요. 메서드 안에서만 사용할 수 있어요. 필터 안에서도 사용되는데, 필터가 실제 구현을 호출하는 지점을 나타내기 위해서예요(필터는 체인을 따라가지 않기로 결정할 수도 있고, 메서드 체인을 따라간 결과를 마음대로 처리할 수도 있어요). next 명령의 결과는 메서드 체인에서 다음 메서드의 결과예요. 메서드 체인에 더 이상 메서드가 없으면 next의 결과는 오류가 돼요. next의 인수 arg는 체인에서 다음 메서드에 전달할 인수예요.

nextto 명령은 next와 같지만, 현재 메서드 체인(자세한 내용은 info object call 참고)에서 사용해야 할 구현을 식별하는 클래스 인수를 하나 더 받는다는 점만 달라요. 선택되는 메서드 구현은 주어진 클래스가 제공하는 것이며, 체인에서 현재 구현보다 더 앞쪽에 있는 기존의 비필터(non-filter) 호출을 가리켜야 해요.

메서드 체인

객체의 메서드가 호출되면 여러 단계가 일어나요:

  1. 객체의 구조(클래스, 슈퍼클래스, 필터, 믹스인)를 조사해 호출할 메서드 구현들의 목록인 메서드 체인(method chain)을 만든다.
  2. 체인의 첫 번째 메서드 구현을 호출한다.
  3. 그 메서드 구현이 next 명령을 호출하면, 다음 메서드 구현을 호출한다(next에 전달된 인수들이 그 인수가 된다).
  4. 전체 메서드 호출의 결과는 가장 바깥쪽 메서드 구현의 결과이고, 안쪽 메서드 구현들은 결과를 next를 통해 돌려준다.
  5. 메서드 체인은 이후 사용을 위해 캐시된다.

메서드 탐색 순서

메서드 체인을 구성할 때, 메서드 구현은 다음 순서로 찾아요:

  • 객체에 믹스인된 클래스들 속에서, 클래스 탐색 순서대로. 믹스인 목록은 자연스러운 순서로 확인한다.
  • 객체의 클래스들에 믹스인된 클래스들 속에서, 믹싱 소스를 클래스 탐색 순서대로. 각 클래스 안에서 믹스인 목록은 자연스러운 순서로 처리된다.
  • 객체 자체에서.
  • 객체의 클래스에서.
  • 클래스의 슈퍼클래스들에서, 각 슈퍼클래스를 슈퍼클래스 목록의 자연스러운 순서대로 깊이 우선(depth-first) 방식으로 따라가며.
  • 특정 메서드 구현은 항상 결과 구현 목록에서 가능한 한 뒤에 온다. 즉 어떤 클래스 A가 클래스 B에 믹스인되어 있고 동시에 B의 슈퍼클래스이기도 하다면, B의 인스턴스는 항상 상속 관점에서 A를 슈퍼클래스로 취급한다. 이는 다중 상속이 간접적으로 처리될 때도 마찬가지다.

필터

객체에 필터 이름 목록이 설정되어 있거나, 필터 이름 목록이 설정된 클래스의 인스턴스이거나(또는 그런 클래스를 믹스인했거나) 하면, 어떤 메서드가 호출되기 전에 그 필터들이 처리돼요. 필터 구현은 클래스 탐색 순서대로, 필터 이름 목록도 마찬가지로(각각 자연스러운 리스트 순서로) 찾아요. 필터로 사용되는 메서드를 명시적으로 호출하면, 그 메서드는 필터로서 한 번, 일반 메서드로서 한 번, 두 번 호출돼요.

각 필터는 실행이 메서드의 실제 구현으로 나아가는 것을 허용할지(필터가 메서드 호출 체인의 앞쪽에 삽입되므로 next 명령을 호출함으로써) 스스로 결정해야 하고, next의 결과를 반환할 책임이 있어요.

필터는 메서드 구현을 찾지 못해 unknown 메서드 호출을 처리할 때 호출되지만, 생성자나 소멸자를 호출할 때는 호출되지 않아요. (다만 destroy 메서드는 일반 메서드이므로, 호출될 때 필터가 평소처럼 호출된다는 점을 기억하세요.)

예제

next로 (슈퍼)클래스의 메서드 구현을 호출하는 방법을 보여주는 예시예요.

oo::class create theSuperclass {
    method example {args} {
        puts "in the superclass, args = $args"
    }
}
oo::class create theSubclass {
    superclass theSuperclass
    method example {args} {
        puts "before chaining from subclass, args = $args"
        next a {*}$args b
        next pureSynthesis
        puts "after chaining from subclass"
    }
}
theSubclass create obj
oo::objdefine obj method example args {
    puts "per-object method, args = $args"
    next x {*}$args y
    next
}
obj example 1 2 3

이 스크립트는 다음과 같이 출력해요:

per-object method, args = 1 2 3
before chaining from subclass, args = x 1 2 3 y
in the superclass, args = a x 1 2 3 y b
in the superclass, args = pureSynthesis
after chaining from subclass
before chaining from subclass, args =
in the superclass, args = a b
in the superclass, args = pureSynthesis
after chaining from subclass

믹스인된 객체의 모든 메서드 호출에 메모이제이션을 적용하는 간단한 캐시 클래스를 만드는 예시예요.

oo::class create cache {
    filter Memoize
    method Memoize args {
        # Do not filter the core method implementations
        if {[lindex [self target] 0] eq "::oo::object"} {
            return [next {*}$args]
        }

        # Check if the value is already in the cache
        my variable ValueCache
        set key [self target],$args
        if {[info exist ValueCache($key)]} {
            return $ValueCache($key)
        }

        # Compute value, insert into cache, and return it
        return [set ValueCache($key) [next {*}$args]]
    }
    method flushCache {} {
        my variable ValueCache
        unset ValueCache
        # Skip the caching
        return -level 2 ""
    }
}

oo::object create demo
oo::objdefine demo {
    mixin cache
    method compute {a b c} {
        after 3000 ;# Simulate deep thought
        return [expr {$a + $b * $c}]
    }
    method compute2 {a b c} {
        after 3000 ;# Simulate deep thought
        return [expr {$a * $b + $c}]
    }
}

puts [demo compute  1 2 3]      → prints "7" after delay
puts [demo compute2 4 5 6]      → prints "26" after delay
puts [demo compute  1 2 3]      → prints "7" instantly
puts [demo compute2 4 5 6]      → prints "26" instantly
puts [demo compute  4 5 6]      → prints "34" after delay
puts [demo compute  4 5 6]      → prints "34" instantly
puts [demo compute  1 2 3]      → prints "7" instantly
demo flushCache
puts [demo compute  1 2 3]      → prints "7" after delay

더 알아보기

  • oo::class, oo::define, oo::object — TclOO 핵심 클래스와 정의
  • self — 현재 객체·메서드 정보 조회