Intent
Modify existing, or add additional
responsibilities to a component without modifying
its original implementation.
Also Known As
...
Example TinyOS Components
CRCFilter, QueuedSend, BufferedLog
Motivation
We often need to add extra functionality to an
existing component, or to modify the way it works
without changing its interfaces. A traditional
object-oriented approach to this problem is to use
inheritance, making the new version a
subclass. However, nesC does not have a component
inheritance mechanism.
For instance, the standard ByteEEPROM
component provides a LogData interface to log
data to a region of flash memory. In some
circumstances, we'd like to introduce a write buffer
on top of the interface, to reduce the number of
actual writes to the EEPROM.
Adding a buffer to the ByteEEPROM
component forces all logging applications to
allocate the buffer; as some application may not
able to spare the RAM, this is
undesirable. Providing two versions, buffered and
unbuffered, replicates code, reducing
reuse. Additionally, it is possible that several
implementers of interface the may benefit from the
added functionality: having multiple copies, spread
across several services, also replicates code.
The Decorator pattern provides a superior
solution. A Decorator component is typically a
module that provides and uses an interface, such as
LogData. Its provided interface adds functionality
on top of the used interface. For example, the
BufferedLog component sits on top of a LogData
provider, and provides the standard LogData
interface, as well as a FastLog interface which
buffers writes.
Using a Decorator has additional
benefits. Separating the added functionality from a
particular implementation allows it to apply to any
implementation. For example, a packet send queue
Decorator can be interposed on top of any ad-hoc
routing implementation.
Applicability
Use the Decorator pattern when:
- You wish to extend the functionality of an
existing component without changing its
implementation, or
- You wish to provide several variants of a
component without having to implement each
possible combination separately.
Structure
Participants
- Old User:
- New User:
- Decorator:
- Original:
Consequences
Applying a Decorator allows you to extend or modify
a component's behavior though a separate component:
the original implementation can remain
unchanged. Additionally, the Decorator can be
applied to any component that provides the
interface.
In most cases, a decorated component should not
be used directly, as the Decorator is already
handling its events. The Placeholder pattern can
ensure this. If a Decorator changes the semantics of
the decorated interface too much, then it will not
be a simple replacement.
Additional interfaces are likely to use the
underlying component, so there will likely be
dependencies between the original and extra
interfaces of a Decorator. For instance, in
BufferedLog, FastLog uses
UnbufferedLog, so concurrent requests to
FastLog and Log are likely to
conflict: only one can access the
UnbufferedLog at once.
Implementation
Sample Code
The standard LogData interface includes
split-phase erase, append and
sync operations. BufferedLog adds
buffering to the LogData operations, and,
additionally, supports a FastLogData
interface with a non-split-phase append
operation (for small writes only):
module BufferedLog {
provides interface LogData as Log;
provides interface FastLogData as FastLog;
uses interface LogData as UnbufferedLog;
}
implementation {
char buffer1[BUFSIZE], buffer2[BUFSIZE];
char *buffer;
command result_t FastLog.append(data, n) {
if (buffer_full) {
call UnbufferedLog.append(buffer, offset);
// ... switch to other buffer ...
}
// ... append to buffer ...
}
}
Known Uses
BufferedLog improves split-phase logging
interface by buffering small writes.
CRCFilter decorates a ReceiveMsg interface by
filtering packets that did not pass a CRC check:
packets that pass are signalled up, those that don't
are not.
QueuedSend decorates a SendMsg interface by
enqueuing multiple requests, which it serlializes
onto an underlying SendMsg, providing in-order
transmission.
Related Patterns
The Adapater pattern can also add functionality
to an existing implementation without changing it,
but does so by changing the interface. The two have
similar, and complementary purposes. A Decorator
allows you to easily modify the behavior of a
service with an interface that you need to use --
within its limits -- while an Adapter allows you to
apply a given service across multiple components
that use similar, but not identical interfaces.
|