Wednesday, February 9, 2011

Structural Patterns - Decorator Pattern

Definition

Attach additional responsibilities or functions to an object dynamically or statically. Also known as Wrapper. 

Motivation

Extending an object’s functionality can be done statically (at compile time) by using inheritance however it might be necessary to extend an object’s functionality dynamically (at runtime) as an object is used.
Consider the typical example of a graphical window. To extend the functionality of the graphical window for example by adding a frame to the window, would require extending the window class to create a FramedWindow class. To create a framed window it is necessary to create an object of the FramedWindow class. However it would be impossible to start with a plain window and to extend its functionality at runtime to become a framed window.

Where to use & benefits



  1. Create a “lowest common denominator” that makes classes interchangeable
  2. Create a second level base class for optional functionality
  3. Core” class and “Decorator” class declare an “isa” relationship
  4. Decorator class “hasa” instance of the “lowest common denominator”
  5. Decorator class delegates to the “hasa” object
  6. Create a Decorator derived class for each optional embellishment
  7. Decorator derived classes delegate to base class AND add extra stuf
  8. Client has the responsibility to compose desired configurations




The decorator pattern helps to add behavior or responsibilities to an object. This is also called “Wrapper”. Suppose we have some 6 objects and 2 of them need a special behavior, we can do this with the help of a decorator.
Java Design Patterns suggest that Decorators should be abstract classes and the concrete implementation should be derived from them.
The decorator pattern can be use wherever there is a need to add some functionality to the object or group of objects. Let’s take an example of a Christmas tree. There is a need to decorate a Christmas tree. Now we have many branches which need to be decorated in different ways.

Let’s have a look at the basic Decorator class. 


Decorator.java
package structural.decorator; public abstract class Decorator {

  /*
* The method places each decorative item
* on the tree.
*/
public abstract void place(Branch branch);
}// End of class
This class has just one method place(). This method places different types of items on the branches of the tree.
The class ChristmasTree is very simple and has just one method which returns a branch.

ChristmasTree.java

package structural.decorator; public class ChristmasTree {

  private Branch branch; public Branch getBranch() {
return branch;
}
}// End of class
Now we can decorate the branches in three different ways, one is by putting coloured balls on them, by putting coloured ruffles on them and also by putting stars on them.
Let’s have a look at the implementation of these three different types of decorators.

BallDecorator.java

package structural.decorator; /**
* Decorates the branch of the tree with
* coloured balls.
*/
public class BallDecorator extends Decorator {

  // Default Constructor
public BallDecorator(ChristmasTree tree) {
Branch branch = tree.getBranch();
place(branch);
}
/*
* The method places each decorative item
* on the tree.
*/
public void place(Branch branch) {
branch.put("ball");
}
}// End of class 

Example

A JScrollPane object can be used to decorate a JTextArea object or a JEditorPane object. A window can be decorated with different borders like BevelBorder, CompoundBorder, EtchedBorder TitledBorder etc. These border classes working as decorators are provided in Java API.
Decorator pattern can be used in a non-visual fashion. For example, BufferedInputStream, DataInputStream, and CheckedInputStream are decorating objects of FilterInputStream class. These decorators are standard Java API classes.
To illustrate a simple decorator pattern in non-visual manner, we design a class that prints a number. We create a decorator class that adds a text to the Number object to indicate that such number is a random number. Of course we can subclass the Number class to achieve the same goal. But the decorator pattern provides us an alternative way.
import java.util.Random;
class Number {
   public void print() {
       System.out.println(new Random().nextInt());
   }
}

class Decorator {
    public Decorator() {
        System.out.print("Random number: ");//add a description to the number printed
        new Number().print();
    }
}

class SubNumber extends Number{
    public SubNumber() {
       super();
       System.out.print("Random number: ");
       print();
    }
}

class Test {
    public static void main(String[] args) {
        new Decorator();
        new SubNumber();
    }
}
java Test
Random number: 145265744
Random number: 145265755
 



// 1. "lowest common denominator"
interface Widget {
  void draw();
}

// 3. "Core" class with "is a" relationship
class TextField implements Widget {
  private int width, height;
  public TextField( int w, int h ) {
    width  = w;
    height = h;
  }
  public void draw() {
    System.out.println( "TextField: " + width + ", " + height );
  }
}

// 2. Second level base class with "isa" relationship
abstract class Decorator implements Widget {
  private Widget wid; // 4. "has a" relationship

  public Decorator( Widget w ) {
    wid = w;
  }

  // 5. Delegation
  public void draw() {
    wid.draw();
  }
}

// 6. Optional embellishment
class BorderDecorator extends Decorator {
  public BorderDecorator( Widget w ) {
    super( w );
  }
  public void draw() {
    super.draw(); // 7. Delegate to base class and add extra stuff
    System.out.println("  BorderDecorator");
  }
}

// 6. Optional embellishment
class ScrollDecorator extends Decorator {
  public ScrollDecorator( Widget w ) {
    super( w );
  }
  public void draw() {
    super.draw(); // 7. Delegate to base class and add extra stuff
    System.out.println( "  ScrollDecorator" );
  }
}

public class DecoratorDemo {
  public static void main( String[] args ) {
    // 8. Client has the responsibility to compose desired configurations
    Widget aWidget = new BorderDecorator(
                       new BorderDecorator(
                         new ScrollDecorator(
                           new TextField( 80, 24 ))));
    aWidget.draw();
  }
}





Example 2 : 

import java.io.*;

public class DecoratorStream {
  static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

  interface LCD {
    void write( String[] s );
    void read(  String[] s );
  }

  static class Core implements LCD {
    public void write( String[] s ) {
      System.out.print( "INPUT:    " );
      try {
        s[0] = in.readLine();
      } catch (IOException ex) { ex.printStackTrace(); }
    }
    public void read( String[] s ) {
      System.out.println( "Output:   " + s[0] );
  } }

  static class Decorator implements LCD {
    private LCD inner;
    public Decorator( LCD i ) { inner = i; }
    public void write( String[] s ) { inner.write( s ); }
    public void read(  String[] s ) { inner.read(  s ); }
  }

  static class Authenticate extends Decorator {
    public Authenticate( LCD inner ) { super( inner ); }
    public void write(  String[] s ) {
      System.out.print( "PASSWORD: " );
      try {
        in.readLine();
      } catch (IOException ex) { ex.printStackTrace(); }
      super.write( s );
    }
    public void read( String[] s ) {
      System.out.print( "PASSWORD: " );
      try {
        in.readLine();
      } catch (IOException ex) { ex.printStackTrace(); }
      super.read( s );
  } }

  static class Scramble extends Decorator {
    public Scramble( LCD inner ) { super( inner ); }
    public void write( String[] s ) {
      super.write( s );
      System.out.println( "encrypt:" );
      StringBuffer sb = new StringBuffer( s[0] );
      for (int i=0; i < sb.length(); i++)
        sb.setCharAt( i, (char) (sb.charAt( i ) - 5) );
      s[0] = sb.toString();
    }
    public void read( String[] s ) {
      StringBuffer sb = new StringBuffer( s[0] );
      for (int i=0; i < sb.length(); i++)
        sb.setCharAt( i, (char) (sb.charAt( i ) + 5) );
      s[0] = sb.toString();
      System.out.println( "decrypt:" );
      super.read( s );
  } }

  public static void main( String[] args ) {
    LCD stream = new Authenticate( new Scramble( new Core() ) );
    String[] str = { new String() };
    stream.write( str );
    System.out.println( "main:     " + str[0] );
    stream.read( str );
} }
PASSWORD: secret INPUT: the quick brown fox encrypt: main: oc`xlpd^fe]mjrizajs PASSWORD: secret decrypt: Output: the quick brown fox

No comments:

Post a Comment