117 lines
5.8 KiB
Racket
117 lines
5.8 KiB
Racket
#lang scribble/doc
|
|
@(require "mz.rkt" (for-label racket/surrogate racket/class))
|
|
|
|
@title{Surrogates}
|
|
|
|
@note-lib-only[racket/surrogate]
|
|
|
|
The @racketmodname[racket/surrogate] library provides an abstraction
|
|
for building an instance of the @deftech{proxy design pattern}. The
|
|
pattern consists of two objects, a @defterm{host} and a
|
|
@defterm{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.
|
|
|
|
@defform/subs[#:literals (augment override override-final)
|
|
(surrogate use-wrapper-proc method-spec ...)
|
|
([use-wrapper-proc #:use-wrapper-proc (code:line)]
|
|
[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])]{
|
|
|
|
If neither @racket[override] nor @racket[override-final] is specified
|
|
for a @racket[method-id], then @racket[override] is assumed.
|
|
|
|
The @racket[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 @racket[#:use-wrapper-proc] does not appear,
|
|
the host mixin adds one field @racket[surrogate].
|
|
to its argument. It also adds getter and setter methods, @racket[get-surrogate],
|
|
@racket[set-surrogate], @racket[get-surrogate-wrapper-proc], and
|
|
@racket[set-surrogate-wrapper-proc] for changing the fields. The
|
|
@racket[set-surrogate] method accepts instances of the class returned by
|
|
the @racket[surrogate] form or @racket[#f], and it updates the field with its
|
|
argument; then, @racket[set-surrogate] calls the @racket[on-disable-surrogate] on the
|
|
previous value of the field and @racket[on-enable-surrogate] for the
|
|
new value of the field. The @racket[get-surrogate] method returns the
|
|
current value of the field.
|
|
|
|
If @racket[#:use-wrapper-proc] does appear, the the host mixin adds
|
|
both the @racket[_surrogate] field (with its getters and setters) and a
|
|
a second field, @racket[_surrogate-wrapper-proc] and its getter and setter
|
|
methods, @racket[_get-surrogate-wrapper-proc] and @racket[_set-surrogate-wrapper-proc].
|
|
The @racket[_surrogate-wrapper-proc] field holds a procedure whose contract
|
|
is @racket[(-> (-> any) (-> any) any)]. The function is invoked with two thunks.
|
|
The first one is a fallback that invokes the original object's method,
|
|
skipping the surrogate. The other one invokes the surrogate.
|
|
@racketblock[(λ (fallback-thunk surrogate-thunk)
|
|
(surrogate-thunk))]
|
|
which means that it simply defers to the method being invoked on the surrogate.
|
|
The @racket[_surrogate-wrapper-proc] capability is part of the surrogate
|
|
so that the dynamic extent of the calls to the surrogate can be adjusted
|
|
(by, for example, changing the values of parameters). The
|
|
@racket[_surrogate-wrapper-proc] is also invoked when calling the
|
|
@racket[_on-disable-surrogate] and @racket[_on-enable-surrogate] methods
|
|
of the surrogate.
|
|
|
|
The host mixin has a single overriding method for each
|
|
@racket[method-id] in the @racket[surrogate] form (even the ones
|
|
specified with @racket[augment]. Each of these
|
|
methods is defined with a @racket[case-lambda] with one arm for each
|
|
@racket[arg-spec]. Each arm has the variables as arguments in the
|
|
@racket[arg-spec]. The body of each method tests the
|
|
@racket[surrogate] field. If it is @racket[#f], the method just
|
|
returns the result of invoking the super or inner method. If the
|
|
@racket[surrogate] field is not @racket[#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.
|
|
|
|
For example, the host-mixin for this surrogate:
|
|
@racketblock[(surrogate (override m (x y z)))]
|
|
will override the @racket[m] method and call the surrogate like this:
|
|
@racketblock[(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)))]
|
|
where @racket[_surrogate] is bound to the value most recently passed
|
|
to the host mixin's @racket[_set-surrogate] method.
|
|
|
|
The host interface has the names @racket[set-surrogate],
|
|
@racket[get-surrogate], and each of the @racket[method-id]s in the
|
|
original form.
|
|
|
|
The surrogate class has a single public method for each
|
|
@racket[method-id] in the @racket[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.
|
|
|
|
In the example above, this is the @racket[_m] method in the surrogate class:
|
|
@racketblock[(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 @racket[super] argument and the super method of the surrogate
|
|
class itself. Only call one or the other, since the default methods
|
|
call the @racket[super] argument.
|
|
|
|
Finally, the interface contains all of the names specified in
|
|
surrogate's argument, plus @racket[on-enable-surrogate] and
|
|
@racket[on-disable-surrogate]. The class returned by
|
|
@racket[surrogate] implements this interface.
|
|
}
|