Result Object Pattern

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.


EditText of this page (last edited July 7, 2000) or FindPage with title or text search