When to (and when not to) use a context object
Steve Freeman recently wrote about some of the perils of passing around a “context” object, from which different parts of the code may extract the collaborators they need to do their job. This approach is one form of “dependency injection”, a technique which decouples code from specific implementations of its collaborating classes and allows any compliant implementation (such as a “stub” or “mock” for testing, an updated version, or simply a version which works differently) to be used instead.
Steve describes an example of using such a context object, then points out some possible problems, then offers some refactoring options to remove the need for the context object in such cases. Within the parameters set by Steve’s post, his solution has merit, but I really don’t feel that the “straw man” problem he raises is representative of all uses of this technique.
The form of context object chosen for Steve’s example might be referred to as a “strongly typed” context. In such usage each object available from the context has its own unique accessor. Addition of a new object to the context requires a change to the context itself. This approach is common when the use of the context object is a direct replacement either for some other form of dependency injection or for a hard-coded object creation in the utilising class.
However, there is another form of context obect, one which might be referred to as a “weakly typed” context. In such usage the context provides general-purpose accessors and the client code is responsible for ensuring (using a cast, introspection, or some other technique) that the retrieved collaborator provides the required facilities. Addition of a new object to the context requires no change to the context itself – typically the object is placed in the context with a name, and a general purpose accessor allows retrieval of any object by name.
In my experience, this form of context is more common in software built using a “core and plugin” architecture. In such an architecture, the “core” is generic, providing features to control or support unknown code in the plugins. This is a powerful and flexible architecture, but if implemented naively can result in the core needing to be changed for every change to, or addition of, a plugin. A more robust approach is to ensure that the core is general enough to work with whatever is needed by all the plugins, even those implemented after the core was written. In order to achieve this, the core must usually have some way of passing information from plugin configurations to plugin invocation, and potentially from one plugin to another. If this is to work for unknown objects, it cannot be expected that every possible configuration value will have its own context accessor.
It is my opinion that in in cases such as this, a weakly-typed context object is still the best choice. Even though any particular client code may use hardly anything from the context, the fact that the context is there, and contains everything required, enables valuable code to continue to be written and used long after the context class was created.
For Steve’s original article, see: Unpack the bag | Steve Freeman