A pattern for
ResultObject from
JamesNoble
You have a computation which returns a complex or important
result. How can you best store and manage this result?
Context
You have a long or important computation, performed by a server
object, and you wish to retain the results of the computation. Perhaps
the computation returns more than one object, or the result is needed
several times or at several places throughout the program. Perhaps you
need to keep information about how the result was obtained, or wish to
avoid the cost of recalculation.
One possible technique to do this is to use lazy initialisation within
the server object that calculates the result. The server is given an
extra variable into which the result can be cached after it is
calculated. When the result is again required, it can be fetched from
the cache variable. More cache variables can be used to return
multiple objects or provide additional information about the result. A
great advantage of lazy initialisation is that it only affects the
server's implementation: the server's protocol is unchanged.
Using lazy initialisation to manage results also has several problems.
It reduces the cohesion of the server, since the server must now store
the calculation as well as compute it, and also increases coupling,
since the result cannot be used independently of the server. This
will increase the size and complexity of the server, especially if
multiple objects must be cached. Finally, multiple independent results
cannot be handled by lazy initialisation, unless the server has some
way to provide access to more than one cached result.
Therefore: Make a new object to package up the entire result of
the computation. Make one instance variable in the
ResultObject for
each value to be returned. If additional information about the result
is required, store this in the
ResultObject also. Provide the usual
accessor messages so that this information can be retrieved from the
ResultObject. Modify the server to create and return a
ResultObject,
and the server's clients to retrieve the results out of the
ResultObject.
Note that this pattern introduces a dynamically created object into the
program, which may increase memory consumption and reduce execution
speed.
Example
A Metric_calculator object for calculating software metrics
provided the following protocol:
m := Metric_calculator computeMetricsFor: anObject.
m sizeOfProtocol.
m numberOfInheritedMethods.
m numberOfOverriddenMethods.
The
computeMetricsFor method initialises the calculator, and
the subsequent methods traverse the object's inheritance hierarchy and
compute metrics. As originally implemented, the traversal was done
whenever an individual metric was required, and repeated should the
result be needed again. Since each metric requires exactly the same
traversal of the inheritance hierarchy, this is in fact a single
computation returning multiple results --- the various different
metrics.
This can be simplified by introducing a
ResultObject. All the metrics
can be calculated in one traversal, and a
ResultObject (called a
Metric_report) returned. Individual metric values can be retrieved
from the Metric_report).
metricReport := Metric_calculator computeMetricsFor: anObject
"now returns a metricReport"
metricReport sizeOfInterface
metricReport numberOfInheritedMethods
metricReport numberOfOverriddenMethods
Known Uses
VisualWorks includes Systemerror objects, package together return both
error codes and identifying parameters from external errors in the
system. The
SelfLanguage Reference Manual describes how objects can be used to
return multiple values from message sends.
See the
ResultObject page for more uses.