6.12 Surrogates
(require racket/surrogate) | package: base |
The racket/surrogate library provides an abstraction for building an instance of the proxy design pattern. The pattern consists of two objects, a host and a surrogate object. The host object delegates method calls to its surrogate object. Each host has a dynamically assigned surrogate, so an object can completely change its behavior merely by changing the surrogate.
syntax
(surrogate use-wrapper-proc method-spec ...)
use-wrapper-proc = #:use-wrapper-proc |
method-spec = (augment method-id arg-spec ...) | (override method-id arg-spec ...) |
(override-final method-id (lambda () default-expr) arg-spec ...) arg-spec = (id ...) | id
The surrogate form produces four values: a host mixin (a procedure that accepts and returns a class), a host interface, a surrogate class, and a surrogate interface.
If #:use-wrapper-proc does not appear, the host mixin adds one field surrogate. to its argument. It also adds getter and setter methods, get-surrogate, set-surrogate, get-surrogate-wrapper-proc, and set-surrogate-wrapper-proc for changing the fields. The set-surrogate method accepts instances of the class returned by the surrogate form or #f, and it updates the field with its argument; then, set-surrogate calls the on-disable-surrogate on the previous value of the field and on-enable-surrogate for the new value of the field. The get-surrogate method returns the current value of the field.
(λ (fallback-thunk surrogate-thunk) (surrogate-thunk))
The host mixin has a single overriding method for each method-id in the surrogate form (even the ones specified with augment. Each of these methods is defined with a case-lambda with one arm for each arg-spec. Each arm has the variables as arguments in the arg-spec. The body of each method tests the surrogate field. If it is #f, the method just returns the result of invoking the super or inner method. If the surrogate field is not #f, the corresponding method of the object in the field is invoked. This method receives the same arguments as the original method, plus two extras. The extra arguments come at the beginning of the argument list. The first is the original object. The second is a procedure that calls the super or inner method (i.e., the method of the class that is passed to the mixin or an extension, or the method in an overriding class), with the arguments that the procedure receives.
(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)))
The host interface has the names set-surrogate, get-surrogate, and each of the method-ids in the original form.
The surrogate class has a single public method for each method-id in the surrogate form. These methods are invoked by classes constructed by the mixin. Each has a corresponding method signature, as described in the above paragraph. Each method just passes its argument along to the super procedure it receives.
(define/public (m original-object original-super x y z) (original-super x y z))
Note: if you derive a class from the surrogate class, do not both call the super argument and the super method of the surrogate class itself. Only call one or the other, since the default methods call the super argument.
Finally, the interface contains all of the names specified in surrogate’s argument, plus on-enable-surrogate and on-disable-surrogate. The class returned by surrogate implements this interface.