[Back to Main Page]

Decorator
Behavioral

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.

[Back to Main Page]

Last modified: Fri Jul 30 17:09:59 PDT 2004