tl;dr Interceptors were popular particularly in distributed systems (CORBA, EJB, DCOM) providing transactional capabilities: an interceptor would begin a transaction, allow the component to carry out its work (usually involving some work against a database), and then commit, abort, or roll back the transaction based on the results of the component's behavior. This concept generalizes well across a variety of different systems, however, and was captured as a pattern in POSA2.

In addition to being useful around distributed system middleware, we see interceptors show up in a number of areas, including some of the more crude "aspect-oriented" implementations that used proxies or code-generation (either compile-time or run-time) to put behavior before, around, or after a component method or field. Additionally, component containers (such as a Java Servlet engine from the Java Enterprise Edition specifications) often provide interceptor functionality (such as "filters" in the Java Servlet specification). Lastly, at the operating system level, it's often been a useful exercise to "intercept" calls to a particular dynamically-loaded library (DLL or libso or dylib) and replace the loaded function with an Interceptor that then decorates/wraps the original call with new behavior.

Problem

Often, behavior is common yet independent of a particular family of objects, and therefore is difficult to capture using traditional object-oriented means. We often look to frameworks and libraries and containers to provide those sorts of cross-cutting behaviors as a result, but frameworks/libraries/containers cannot anticipate all of the services/behavior that they will be expected to offer clients. In many cases, that behavior might also be outside the realm of the framework/library/container's area of responsibility, and so capturing it inside that framework or library or container would be a semantic violation of encapsulation.

Context

Solution

Allow clients to extend a framework/library/container transparently by registering 'out-of-band' services or behavior via predefined "hook points", then let the framework/library/container trigger these services automatically when certain events occur.

In detail: for a designated set of events processed by a framework, specify and expose an interceptor callback interface. This callback interface can be either a traditional O-O interface (so that interest parties can use inheritance to gain type-safety and -compatibility) or it can be a named first-class function reference. Applications can derive concrete interceptors from this interface, or provide Strategy-based/first-class-function objects, to implement out-of-band services that process occurrences of these events in an application-specific manner. Provide a dispatcher for each interceptor that allows applications to register their concrete interceptors with the framework. When the designated events occur, the framework notifies the appropriate dispatchers to invoke the callbacks of the registered concrete interceptors.

Then, to create an interceptor, create a block of code that can be "hooked" into place (sometimes silently, replacing the original component's method, such that callers to the original component's method are now invoking the Interceptor). The Interceptor can (and should) have access to the parameters passed in to the method call, but generally also pass those parameters (possibly modified) on to the original component's method for processing, and take the return value from that processing and hand it back (possibly modified) to the original caller.

Implementations

Consequences

Relationships

Interceptors will often be passed or have access to a Context Object as part of the call for support, in which can be found the parameters used to invoke the original component to be available, as well as any additional supporting data or functionality. Context objects can also allow a concrete interceptor to introspect and control certain aspects of the framework's internal state and behavior in response to events.

If the Interceptor is a "silent" replacement of the original method, it obeys the same call signature (explicit or implicit) of the orignal maethod, and thus provides a common footprint for multiple Interceptors to "stack", one after the other, each providing some specific behavior to a variety of different components that otherwise have nothing to do with each other, thus forming a chain like a Chain of Responsibility, but where the Chain generally looks for the first handler and aborts the remainder of the chain, interceptors are usually all guaranteed a chance to run and provide the behavior desired.

If the Interceptor is a "silent" replacement of the original, Interceptors are often modeled using Proxy, but the use of a proxy object is not required--only that the original component's behavior is adjusted. Proxies can often hold some interesting state of use only to the interceptor, but that can also be held via Closure-Based State in situations where the original object must be maintained in place.

Dynamic Objects are particularly easy to use in conjunction with Interceptors, as the meta-object replacement of the object's behavior with the Interceptor is usually a trivial operation--however, the interceptor must be careful to retain a reference to the original behavior if the original behavior is to remain accessible to the Interceptor.

If all of an object's methods are valid targets for an Interceptor, and the additional behavior is known at compile-time (or at least at the time of instantiation), a Decorator may in fact be a more effective and/or efficient means of behavioral supplementation.

Interceptors are often made out of a Continuation Chain, where the Interceptor is passed an explicit function parameter as the next step in the chain. This provides flexibility for the Interceptor to choose not to pass the call onwards, essentially using the Interceptor as more of a Chain of Responsibility, but this also runs the risk of a poorly-written Interceptor not invoking the next step in the chain as it is expected to, and thereby breaking expectations.

Variations

Last updated: 25 March 2016

Tags: pattern   structural