서러게이트
서러게이트 (Surrogates)
프록시(proxy) 디자인 패턴의 인스턴스를 만드는 추상화를 제공하는 racket/surrogate 라이브러리를 살펴볼게요. 호스트 객체가 메서드 호출을 서러게이트 객체에 위임하는 방식으로 동작해요.
출처: Racket Reference
본문
(require racket/surrogate) | package: base
이 절에 문서화된 바인딩들은 racket/surrogate 라이브러리가 제공하는 것이지, racket/base나 racket이 제공하는 게 아니에요.
racket/surrogate 라이브러리는 프록시 디자인 패턴의 인스턴스를 만드는 추상화를 제공해요. 이 패턴은 두 객체, 즉 호스트(host)와 서러게이트(surrogate) 객체로 이루어져요. 호스트 객체는 메서드 호출을 자신의 서러게이트 객체에 위임해요. 각 호스트는 동적으로 할당된 서러게이트를 가지므로, 객체는 서러게이트만 바꿔서 자신의 동작을 완전히 바꿀 수 있어요.
syntax
(surrogate use-wrapper-proc method-spec ...)
use-wrapper-proc = #:use-wrapper-proc
|
method-spec = (augment default-expr method-id arg-spec ...)
| (override method-id arg-spec ...)
arg-spec = (id ...)
| id
surrogate 폼은 네 개의 값을 만들어 내요: 호스트 믹스인(클래스를 받아 클래스를 돌려주는 프로시저), 호스트 인터페이스, 서러게이트 클래스, 서러게이트 인터페이스.
#:use-wrapper-proc가 나타나지 않으면, 호스트 믹스인은 자신의 인자에 private 필드 하나를 추가해요. 그리고 그 필드의 값을 가져오고 설정하는 getter·setter 메서드 get-surrogate와 set-surrogate도 추가해요. set-surrogate 메서드는 surrogate 폼이 돌려주는 클래스의 인스턴스나 #f를 받고, 그 인자로 필드를 갱신해요. 그러고 나서 set-surrogate는 이전 필드 값에 대해 on-disable-surrogate를 호출하고, 새 필드 값에 대해 on-enable-surrogate를 호출해요. get-surrogate 메서드는 현재 필드 값을 돌려줘요.
#:use-wrapper-proc가 나타나면, 호스트 믹스인은 private 필드 하나를 더 추가하고, 그 getter·setter 메서드 get-surrogate-wrapper-proc와 set-surrogate-wrapper-proc도 추가해요. 추가 필드는 래퍼 프로시저를 담는데, 그 계약은 (-> (-> any) (-> any) any)예요. 즉 프로시저는 두 개의 썽크(thunk)와 함께 호출돼요. 첫 번째 썽크는 서러게이트를 건너뛰고 원래 객체의 메서드를 호출하는 폴백(fallback)이에요. 두 번째 썽크는 서러게이트를 호출해요. 기본 래퍼 프로시저는
(λ (fallback-thunk surrogate-thunk)
(surrogate-thunk))
이에요. 즉 그저 서러게이트에 호출되는 메서드로 위임해요. 래퍼 프로시저는 예를 들어 파라미터의 값을 바꾸는 방식으로 서러게이트에 대한 호출의 동적 범위(dynamic extent)를 조정할 수 있다는 점을 참고하세요. 래퍼 프로시저는 서러게이트의 on-disable-surrogate와 on-enable-surrogate 메서드를 호출할 때도 호출돼요.
호스트 믹스인은 surrogate 폼 안의 각 method-id(심지어 augment로 지정된 것들까지도)에 대해 단일 오버라이딩 메서드를 가져요. 이 메서드들은 각각 하나의 arg-spec마다 하나의 arm을 가진 case-lambda로 정의돼요. 각 arm은 arg-spec 안의 변수들을 인자로 가져요. 각 메서드의 본문은 private 서러게이트 필드를 검사해요. 필드 값이 #f면 메서드는 그냥 super·inner 메서드를 호출한 결과를 돌려줘요. 필드 값이 #f가 아니면 필드 안 객체의 해당 메서드가 호출돼요. 이 메서드는 원래 메서드와 같은 인자에 추가로 두 개를 더 받아요. 추가 인자는 인자 리스트의 맨 앞에 와요. 첫 번째는 원래 객체이고, 두 번째는 (믹스인이나 확장에 넘겨진 클래스의, 또는 오버라이딩 클래스의 메서드인) super·inner 메서드를, 프로시저가 받는 인자와 함께 호출하는 프로시저예요.
예를 들어, 이 서러게이트에 대한 호스트 믹스인은
(surrogate (override m (x y z)))
m 메서드를 오버라이드하고 서러게이트를 이렇게 호출해요:
(define/override (m x y z)
(if surrogate
(send surrogate m
this
(λ (x y z) (super m x y z))
x y z)
(super m x y z)))
여기서 surrogate는 호스트 믹스인의 set-surrogate 메서드에 가장 최근에 넘겨진 값에 바인딩돼요.
호스트 인터페이스는 set-surrogate, get-surrogate, 그리고 원래 폼 안의 각 method-id 이름을 가져요.
서러게이트 클래스는 surrogate 폼 안의 각 method-id에 대해 공개 메서드 하나를 가져요. 이 메서드들은 믹스인이 만든 클래스가 호출해요. 각각은 위 문단에서 설명한 대로 대응하는 메서드 시그니처를 가지며, 각 메서드는 받은 인자를 자신이 받는 super 프로시저로 그냥 넘겨줘요.
위 예시에서 이것이 서러게이트 클래스의 m 메서드예요:
(define/public (m original-object original-super x y z)
(original-super x y z))
서러게이트 클래스에서 클래스를 파생할 때는 super 인자와 서러게이트 클래스 자체의 super 메서드를 둘 다 호출하지 마세요. 기본 메서드들이 super 인자를 호출하므로, 둘 중 하나만 호출해야 해요.
마지막으로 인터페이스는 surrogate의 인자에 지정된 모든 이름에 on-enable-surrogate와 on-disable-surrogate를 더한 것을 담아요. surrogate가 돌려주는 클래스가 이 인터페이스를 구현해요.