Friday, September 4, 2009

Patterns - 023: Structural patterns ( Decorator )


The Decorator Pattern is used to extend the functionality of an object dynamically without having to change the original class source or using inheritance. This is accomplished by creating an object wrapper referred to as a Decorator around the actual object.

  • The Decorator object is designed to have the same interface as the underlying object. This allows a client object to interact with the Decorator object in exactly the same manner as it would with the underlying actual object.
  • The Decorator object contains a reference to the actual object.
  • The Decorator object receives all requests (calls) from a client. It in turn forwards these calls to the underlying object.
  • The Decorator object adds some additional functionality before or after forwarding requests to the underlying object. This ensures that the additional functionality can be added to a given object externally at runtime without modifying its structure.

Decorator PatternInheritance

Used to extend the functionality of a particular object.

Used to extend the functionality of a class of objects.

Does not require subclassing.

Requires subclassing.

Dynamic.

Static.

Runtime assignment of responsibilities.

Compile time assignment of responsibilities.

Prevents the proliferation of subclasses leading to less complexity and confusion.

Could lead to numerous subclasses, exploding class hierarchy on specific occasions.

More flexible.

Less flexible.

Possible to have different decorator objects for a given object simultaneously. A client can choose what capabilities it wants by sending messages to an appropriate decorator.

Having subclasses for all possible combinations of additional capabilities, which clients expect out of a given class, could lead to a proliferation of subclasses.

Easy to add any combination of capabilities. The same capability can even be added twice.

Difficult.

Let us suppose that some of the clients are now in need of logging messages in new ways beyond what is offered by the message logging utility. Let us consider the following two small features that clients would like to have:


  • Transform an incoming message to an HTML document.
  • Apply a simple encryption by transposition logic on an incoming message.



Typically, in object-oriented design, without changing the code of an existing class, new functionality can be added by applying inheritance, i.e., by subclassing an existing class and overriding its methods to add the required new functionality. Applying inheritance, we would subclass both the FileLogger and the ConsoleLogger classes to add the new functionality with the following set of new subclasses.




As can be seen from the class in above diagram , a set of four new subclasses are added in order to add the new functionality. If we had additional Logger types (for example a DBLogger to log messages to a database), it would lead to more subclasses. With every new feature that needs to be added, there will be a multiplicative growth in the number of subclasses and soon we will
have an exploding class hierarchy.

The Decorator pattern comes to our rescue in situations like this. The Decorator pattern recommends having a wrapper around an object to extend its functionality by object composition rather than by inheritance.


Applying the Decorator pattern, let us define a default root decorator LoggerDecorator for the message logging utility with the following characteristics:





   public class LoggerDecorator implements Logger {

       Logger logger;

       public LoggerDecorator(Logger inp_logger) {
           logger = inp_logger;
       }

       public void log(String DataLine) {
           /*
               Default implementation
               to be overriden by subclasses.
           */
           logger.log(DataLine);
       }

   }

  • The LoggerDecorator contains a reference to a Logger instance. This reference points to a Logger object it wraps.
  • The LoggerDecorator implements the Logger interface and provides the basic default implementation for the log method, where it simply forwards an incoming call to the Logger object it wraps. Every subclass of the LoggerDecorator is hence guaranteed to have the log method defined in it.

Applying Decorator




  • Create an appropriate Logger instance (FileLogger/ConsoleLogger) using the LoggerFactory factory method.
  • Create an appropriate LoggerDecorator instance by passing the Logger instance created in Step 1 as an argument to its constructor.
  • Invoke methods on the LoggerDecorator instance as it would on the Logger instance.




           public class HTMLLogger extends LoggerDecorator {

               public HTMLLogger(Logger inp_logger) {
                   super(inp_logger);
               }

               public void log(String DataLine) {
                   /*
                       Added functionality
                   */
                   DataLine = makeHTML(DataLine);

                   /*
                       Now forward the encrypted text to the
                       FileLogger  for storage
                   */
                   logger.log(DataLine);
               }

               public String makeHTML(String DataLine) {
                   /*
                       Make it into an HTML document.
                   */
                   DataLine = "<HTML><BODY>" +
                              "<b>" + DataLine +
                              "</b>" +
                              "</BODY></HTML>";


                   return DataLine;
               }
           }
       

From the example it can be observed that a LoggerDecorator instance contains a reference to an object of type Logger. It forwards requests to this Logger object before or after adding the new functionality. Since the base LoggerDecorator class implements the Logger interface, an instance of LoggerDecorator or any of its subclasses can be treated as of the Logger type. Hence a LoggerDecorator can contain an instance of any of its subclasses and forward calls to it. In general, a decorator object can contain another decorator object and can forward calls to it. In this way, new decorators, and hence new functionality, can be built by wrapping an existing decorator object.



               class DecoratorClient {

                   public static void main(String[] args) {
                       LoggerFactory factory = new LoggerFactory();
                       Logger logger = factory.getLogger();

                       HTMLLogger hLogger = new HTMLLogger(logger);

                       //the decorator object provides the same interface.
                       hLogger.log("A two.eight.Message to Log");

                       EncryptLogger eLogger = new EncryptLogger(logger);
                       eLogger.log("A two.eight.Message to Log");
                   }

               }
           

No comments:

Post a Comment