Intent
Enables you to easily change which implementation
of a component is used across an entire
application. This can prevent inadvertent inclusion
of multiple, incompatible implementations.
Also Known As
Pass-through, wrapper
Example TinyOS Components
...
Motivation
Often, a given component has several variant
implementations. For example, a locking interface
may have semantic requirements on its operations,
such as requiring that only the component that holds
a lock can unlock it. One implementation could
assume that all calls are made properly, while a
second one could add additional run-time checks.
The decision of which implementation to use has
to be a system-wide one. Otherwise, different parts
of the application may acquire locks on separate
implementations, which would defeat the purpose of
mutual exclusion. However, every configuration that
wires the component names it; changing
implementation could require changing many files,
spread out over many subsystems.
One option is to implement the two versions with
the same component name, and put them in separate
directories. Manipulating the nesC search order
could allow you to select which version to use.
While this approach may work for one or two
options, it doesn't scale well to many: each
implementation of each component needs a separate
directory. Streamlining this structure by bundling
several implementations (e.g., the "safe" version)
in a single directory requires all-or-nothing
inclusion.
The Placeholder pattern offers a solution. The
desired component is represented by a placeholder
configuration: all components that need to use the
component wire to the placeholder. The placeholder
itself is just a simple-pass through wiring to a
concrete implementation. Changing which
implementation is used requires changing only which
one the placeholder represents. The additional level
of indirection is merely at the language level, and
the compiler optimizes it away.
Applicability
Use the Placeholder pattern when:
- A component or service has multiple, mutually
exclusive implementations.
- Many subsystems and parts of your application
need to use this component/service.
- You need to be able to easily switch between
implementations.
Structure
Participants
- Placeholder: the component that all
other components wire to. It encapsulates the
implementation and exports its interfaces with
pass-through wiring. It has the same signature as
the Implementation component.
- Implementation: the specific version of
the component.
- Users: components that want to use the
functionality the abstraction provides.
- Service: components providing
functionality that the that abstraction depends
on.
Collaborations
Consequences
Using the Placeholder pattern generally requires that
every component in an application wire to the Placeholder
instead of a concrete instance. Otherwise, there
could be multiple implementations used
concurently. Incorporating a Placeholder into an existing
application can therefore require modifying many
components.
Althlough a Placeholder adds another level of wiring
indirection, the nesC compiler optimizes this away
into a direct function call.
Implementation
Sample Code
Imagine an application that uses ad-hoc
collection routing to collect and aggregate sensor
readings. Several parts of the application --
sensing, time synchronization, power management --
may all need to interact with the routing
protocol. However, the application is designed to be
independent of the routing implementation, so that
improvements or new algorithms can be easily
incorporated.
In this case, the routing subsystem can be
represented by a Placeholder, which provides a unified
name for the underlying implementation:
configuration CollectionRouter {
provides {
interface StdControl;
interface RouteControl;
interface Send[uint8_t id];
interface Receive[uint8_t id];
interface Intercept[uint8_t id];
}
implementation {
component MultiHopRouter as Router;
StdControl = Router;
RouteControl = Router;
Send = Router;
Receive = Router;
Intercept = Router;
}
Every component that uses multihop routing wires
to CollectionRouter:
configuration Sensing {
...
}
implementation {
components SensingM, CollectionRouter;
...
SensingM.Send = CollectionRouter.Send[AM_AGGDATAMSG];
SensingM.Receive = CollectionRouter.Receive[AM_AGGDATAMSG];
SensingM.Intercept = CollectionRouter.Intercept[AM_AGGDATAMSG];
...
}
Changing the underlying routing implementation
for all components that use it requires changing a
single line in CollectionRouter, which then
reconfigures the entire application.
Known Uses
Related Patterns
The Facade pattern also encapsulates a set
of functionality inside a configuration, but it does
so to provide several services using a unified
interface. In contrast, the Placeholder pattern
represents a single service for which there are
multiple, incompatible implementations. The Facade
deals with the granularity at which programmers deal
with services, while the Placeholder deals with how
those services are chosen and named.
|