racket/pkgs/racket-doc/scribblings/reference/surrogate.scrbl
2016-12-21 15:12:15 -06:00

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.
}