Difference between revisions of "Decorator"

From CDOT Wiki
Jump to: navigation, search
m (Example)
 
(40 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
[[BTP600]] > [[Decorator]]
 +
 
Allow new/additional responsibility to be added to an existing object dynamically. Decoratores provide a flexible alternative to subclassing for extending functionality.
 
Allow new/additional responsibility to be added to an existing object dynamically. Decoratores provide a flexible alternative to subclassing for extending functionality.
  
Line 4: Line 6:
  
 
==Introduction==
 
==Introduction==
* structual pattern
+
Decorator, a [http://en.wikipedia.org/wiki/Structural_pattern structual pattern] also known as wrapper, adds additional functionality to a class at runtime through composition. Decorators are alternative to subclassing which add behaviour at compile time. By wrapping, it allows us to add things to the component without requiring every subclass to inherit these new qualities. Each decorator class wraps a component, which means the decorator has an instance variable that holds a reference to a component.
* aka Wrapper
 
* allows us to add things to a component without requiring every subclass to inherit these new qualities.
 
* Function added at runtime through composition.
 
* We want an object to do something new, not its class
 
*
 
  
 
==Motivation==
 
==Motivation==
 +
As an example we can look at a coffee shop ordering service where beverages can be decorated with different kinds of condiments. By using the Decorator pattern one can get their beverage just right and the cost can be easily calculated for every added condiment. Adding new beverages/condiments is easy too, just by creating the new class and changing the menu.
  
 
==Sample Code==
 
==Sample Code==
window/scrolling scenario
+
[[Image:beveragedecorator.png|300 px|thumb|Beverage Decorator UML diagram]]
 +
Beverage is the abstract component class
 +
public abstract class Beverage {
 +
    String description = "Unknown Beverage";
 +
 +
    public String getDescription() {
 +
      return description;
 +
    }
 +
 +
    public abstract double cost();
 +
    //static abstract double size();
 +
}
 +
 
 +
[[Image:wrappingdecorator2.png|300 px|thumb|Figure 1 wrapping Decorator classes]]
  
// the Window interface
+
Leaf classes that inherit from Beverage class
  interface Window {
+
  public class DarkRoast extends Beverage {
     public void draw(); // draws the Window
+
     public DarkRoast() {
     public String getDescription(); // returns a description of the Window
+
      description = "Dark Roast Coffee";
 +
    }
 +
 +
     public double cost() {
 +
      return .99;
 +
    }
 
  }
 
  }
+
 
// implementation of a simple Window without any scrollbars
+
  public class Decaf extends Beverage {
  class SimpleWindow implements Window {
+
     public Decaf() {
     public void draw() {
+
      description = "Decaf Coffee";
        // draw window
 
 
     }
 
     }
 
   
 
   
     public String getDescription() {
+
     public double cost() {
        return "simple window";
+
      return 1.05;
 
     }
 
     }
 
  }
 
  }
  
The decorator classes
+
[[Image:beverageDecoratorWholePic.png|300 px|thumb|The Big Picture]]
 +
 
 +
Condiment Decorator class
 +
public abstract class CondimentDecorator extends Beverage {
 +
    public abstract String getDescription();
 +
}
  
// abstract decorator class - note that it implements Window
+
Mocha is a concrete decorator class that implements cost() and getDescription()
  abstract class WindowDecorator implements Window {
+
  public class Mocha extends CondimentDecorator {
     protected Window decoratedWindow; // the Window being decorated
+
     Beverage beverage;
 
   
 
   
     public WindowDecorator (Window decoratedWindow) {
+
     public Mocha(Beverage beverage) {
        this.decoratedWindow = decoratedWindow;
+
      this.beverage = beverage;
 
     }
 
     }
}
 
 
   
 
   
// the first concrete decorator which adds vertical scrollbar functionality
+
     public String getDescription() {
class VerticalScrollBarDecorator extends WindowDecorator {
+
      return beverage.getDescription() + ", Mocha";
     public VerticalScrollBarDecorator (Window decoratedWindow) {
 
        super(decoratedWindow);
 
 
     }
 
     }
+
   
     public void draw() {
+
     public double cost() {
        drawVerticalScrollBar();
+
    return 0.10 + beverage.cost();
        decoratedWindow.draw();
 
 
     }
 
     }
 +
}
 +
 +
Another concrete decorator
 +
public class Vanilla extends CondimentDecorator {
 +
    Beverage beverage;
 
   
 
   
     private void drawVerticalScrollBar() {
+
     public Vanilla(Beverage beverage) {
        // draw the vertical scrollbar
+
      this.beverage = beverage;
 
     }
 
     }
 
   
 
   
 
     public String getDescription() {
 
     public String getDescription() {
        return decoratedWindow.getDescription() + ", including vertical scrollbars";
+
      return beverage.getDescription() + ", Vanilla";
 
     }
 
     }
}
 
 
   
 
   
// the second concrete decorator which adds horizontal scrollbar functionality
+
     public double cost() {
class HorizontalScrollBarDecorator extends WindowDecorator {
+
    return 0.20 + beverage.cost();
     public HorizontalScrollBarDecorator (Window decoratedWindow) {
 
        super(decoratedWindow);
 
 
     }
 
     }
 +
}
 +
 +
The main: Here we are creating a new Decaf object with no condiments and printing its description and cost.
 +
The second beverage is created and we call it DarkRoast, we add two shots of Mocha and a shot of vanilla.
 +
Note how the beverage object is passed into the condiment decorator, this is how wrapping works.
 +
DarkRoast is wrapped with Mocha first, the Mocha object stores a reference to the beverage passed in.
 +
This is demonstrated in Figure 1.
 +
public class StarbuzzCoffee {
 +
    public static void main(String args[]) {
 +
      Beverage beverage = new Decaf();
 +
      System.out.println(beverage.getDescription() + " $" + beverage.cost());
 
   
 
   
    public void draw() {
+
      Beverage beverage2 = new DarkRoast();
        drawHorizontalScrollBar();
+
      beverage2 = new Mocha(beverage2);
        decoratedWindow.draw();
+
      beverage2 = new Mocha(beverage2);
 +
      beverage2 = new Vanilla(beverage2);
 +
      System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
 
     }
 
     }
 +
}
 +
<!--
 +
{| align="left"
 +
|-
 +
[[Image:beveragedecorator.png|300 px|Beverage Decorator UML diagram]]
 +
[[Image:wrappingdecorator.png|300 px|Figure 1 wrapping Decorator classes]]
 +
[[Image:beverageDecoratorWholePic.png|300 px|Whole Picture]]
 +
|}
 +
-->
 +
 +
==Example==
 +
[[Image:javaIOdecorator2.png|500 px|thumb|Java I/O classes]]
 +
 +
Java I/O [http://java.sun.com/javase/6/docs/api/java/io/package-frame.html API]
 +
 +
JDK 6u1 [http://www.java.net/download/jdk6/6u1/promoted/b03/jdk-6u1-ea-src-b03-jrl-19_jan_2007.jar Source] under the [http://www.java.net/jrl.csp JRL license]
 +
 +
extract: java -jar jdk-6u1-ea-src-b03-jrl-19_jan_2007.jar
 +
 +
java.io location: j2se/src/share/classes/java/io/
 +
 +
----
 +
Abstract Component class:
 +
public abstract class InputStream {
 +
    abstract int read();
 
   
 
   
     private void drawHorizontalScrollBar() {
+
}
         // draw the horizontal scrollbar
+
Concrete Component class:
 +
class FileInputStream extends InputStream
 +
{
 +
    public int read();
 +
}
 +
Decorator class:
 +
public
 +
class FilterInputStream extends InputStream {
 +
{
 +
    //<span style="color:red">Local variable for storing the reference of the concrete component class</span>
 +
    protected volatile InputStream in;
 +
 +
    protected FilterInputStream(InputStream in) {
 +
      this.in = in;
 +
    }
 +
   
 +
     public int read() {
 +
      return in.read();
 +
    }
 +
}
 +
Concrete Decorator class:
 +
public
 +
class PushbackInputStream extends FilterInputStream { 
 +
    public PushbackInputStream(InputStream in, int size) {
 +
         super(in); //<span style="color:red">Calls the FilterInputStream class and stores the reference of InputStream</span>
 +
        if (size <= 0) {
 +
            throw new IllegalArgumentException("size <= 0");
 +
        }
 +
        this.buf = new byte[size];
 +
        this.pos = size;
 
     }
 
     }
 
   
 
   
     public String getDescription() {
+
     public int read(){
         return decoratedWindow.getDescription() + ", including horizontal scrollbars";
+
         ensureOpen();
 +
        if (pos < buf.length) {
 +
          return buf[pos++] & 0xff;
 +
        }
 +
        return super.read();
 
     }
 
     }
 
  }
 
  }
  
main program
+
----
  public class DecoratedWindowTest {
+
 
    public static void main(String[] args) {
+
[[Image:mpclDecorator.png|400 px|thumb|MPCL]]
        // create a decorated Window with horizontal and vertical scrollbars
+
 
        Window decoratedWindow = new HorizontalScrollBarDecorator (
+
Multi purpose class library (MPCL)
                new VerticalScrollBarDecorator(new SimpleWindow()));
+
 
 +
[http://www.google.com/codesearch?hl=en&q=show:QUdC7Hs10WU:9dcfaUrTzRs:gcPYjQUkI_I&sa=N&ct=rd&cs_p=http://www.uesqlc.org/download/mpcl/mpcl-11.0.2.zip&cs_f=mpcl-11.0.2/src/java/lib/org/mpcl/nui/table/TDecoratorTableCellRenderer.java  Source Code]
 +
 
 +
GPL license
 +
 
 +
----
 +
 
 +
Concrete Components <br>
 +
TDecoratorTableCellRenderer:
 +
/**
 +
*  Table cell renderer that  decorates a delegate cell renderer.  It implements
 +
*  the Decorator design pattern.
 +
*/
 +
  public class TDecoratorTableCellRenderer implements TableCellRenderer
 +
{
 +
 +
  /// Delegate table cell renderer.
 +
  private TableCellRenderer  tDelegateTableCellRenderer;
 +
 +
  ///  Table cell decorator.
 +
  private ITableCellDecorator  tTableCellDecorator;
 +
 +
 +
  //
 +
  //  C O N S T R U C T O R S
 +
  //
 
   
 
   
         // print the Window's description
+
  /**
         System.out.println(decoratedWindow.getDescription());
+
  *  Builds a new instance.
 +
  *  @param tDELEGATE_TABLE_CELL_RENDERER The delegate table cell renderer.
 +
  *  @param tTABLE_CELL_DECORATOR         Table cell decorator.
 +
  */
 +
  public TDecoratorTableCellRenderer ( TableCellRenderer  tDELEGATE_TABLE_CELL_RENDERER ,
 +
                                      ITableCellDecorator tTABLE_CELL_DECORATOR        )
 +
  {
 +
    tDelegateTableCellRenderer = tDELEGATE_TABLE_CELL_RENDERER;
 +
    tTableCellDecorator        = tTABLE_CELL_DECORATOR;
 +
  }
 +
 
 +
 
 +
DefaultTableCellRenderer:
 +
public class DefaultTableCellRenderer extends JLabel
 +
    implements TableCellRenderer, Serializable
 +
{
 +
    /**
 +
    * Creates a default table cell renderer.
 +
    */
 +
    public DefaultTableCellRenderer() {
 +
         super();
 +
        setOpaque(true);
 +
        setBorder(getNoFocusBorder());
 
     }
 
     }
 
  }
 
  }
  
==Example==
+
TDateTableCellRenderer:
*Decorating our Beverages
+
public class TDateTableCellRenderer extends DefaultTableCellRenderer
*Java I/O
+
{
 +
  ///  Date format.
 +
  private DateFormat  tDateFormat;
 +
 +
  //
 +
  //  C O N S T R U C T O R S
 +
  //
 +
 +
  /**
 +
  *  Sets the String object for the cell being rendered to value.
 +
  *  @param tVALUE The value for this cell; if \a tVALUE is \a null it sets the
 +
  *                the text value to an empty string.
 +
  *  @see javax.swing.table.DefaultTableCellRenderer#setValue()
 +
  */
 +
  protected void setValue (Object tVALUE)
 +
  {
 +
    setText (( tVALUE == null ) ? "" : tDateFormat.format ((Date) tVALUE));
 +
  }
 +
 
 +
TPopulationTableCellRenderer:
 +
///  Table cell renderer for \a TPopulation objects.
 +
public class TPopulationTableCellRenderer extends DefaultTableCellRenderer
 +
{
 +
 +
  ///  Number format.
 +
  public NumberFormat  tNumberFormat;
 +
 +
  //
 +
  //  C O N S T R U C T O R S
 +
  //
 +
 +
  /**
 +
  *  Sets the String object for the cell being rendered to value.
 +
  *  @param tVALUE The value for this cell; if \a tVALUE is \a null it sets the
 +
  *                the text value to an empty string.
 +
  *  @see javax.swing.table.DefaultTableCellRenderer#setValue()
 +
  */
 +
  protected void setValue (Object tVALUE)
 +
  {
 +
    if ( tNumberFormat == null)
 +
    {
 +
      tNumberFormat = TDecimalFormat._getPopulationInstance();
 +
    }
 +
    setText (( tVALUE == null ) ? "" : tNumberFormat.format (((TPopulation) tVALUE).intValue()));
 +
  }
 +
 +
  //
 +
  // C O N S T R U C T O R S
 +
  //
 +
 +
  ///  Builds a new instance.
 +
  public TPopulationTableCellRenderer()
 +
  {
 +
    super();
 +
    setHorizontalAlignment (DefaultTableCellRenderer.RIGHT);
 +
  }
 +
 +
}  // class TPopulationTableCellRenderer
 +
 
 +
==Contributions==
 +
[http://zenit.senecac.on.ca/wiki/index.php/Talk:Adapter Adapter pattern] in Eclipse
  
 
==References==
 
==References==
 
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2.
 
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2.
 +
 +
Freeman, Eric; Freeman, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Head First Design Patterns. O`Reilly. ISBN 0-596-00712-4.
 +
 
http://www.dofactory.com/Patterns/PatternDecorator.aspx
 
http://www.dofactory.com/Patterns/PatternDecorator.aspx

Latest revision as of 14:33, 11 April 2007

BTP600 > Decorator

Allow new/additional responsibility to be added to an existing object dynamically. Decoratores provide a flexible alternative to subclassing for extending functionality.

UML Class diagram of the decorator pattern

Introduction

Decorator, a structual pattern also known as wrapper, adds additional functionality to a class at runtime through composition. Decorators are alternative to subclassing which add behaviour at compile time. By wrapping, it allows us to add things to the component without requiring every subclass to inherit these new qualities. Each decorator class wraps a component, which means the decorator has an instance variable that holds a reference to a component.

Motivation

As an example we can look at a coffee shop ordering service where beverages can be decorated with different kinds of condiments. By using the Decorator pattern one can get their beverage just right and the cost can be easily calculated for every added condiment. Adding new beverages/condiments is easy too, just by creating the new class and changing the menu.

Sample Code

Beverage Decorator UML diagram

Beverage is the abstract component class

public abstract class Beverage {
   String description = "Unknown Beverage";

   public String getDescription() {
      return description;
   }

   public abstract double cost();
   //static abstract double size();
}
Figure 1 wrapping Decorator classes

Leaf classes that inherit from Beverage class

public class DarkRoast extends Beverage {
   public DarkRoast() {
      description = "Dark Roast Coffee";
   }

   public double cost() {
      return .99;
   }
}
public class Decaf extends Beverage {
   public Decaf() {
      description = "Decaf Coffee";
   }

   public double cost() {
      return 1.05;
   }
}
The Big Picture

Condiment Decorator class

public abstract class CondimentDecorator extends Beverage {
   public abstract String getDescription();
}

Mocha is a concrete decorator class that implements cost() and getDescription()

public class Mocha extends CondimentDecorator {
   Beverage beverage;

   public Mocha(Beverage beverage) {
      this.beverage = beverage;
   }

   public String getDescription() {
      return beverage.getDescription() + ", Mocha";
   }
   
   public double cost() {
    	return 0.10 + beverage.cost();
   }
}

Another concrete decorator

public class Vanilla extends CondimentDecorator {
   Beverage beverage;

   public Vanilla(Beverage beverage) {
      this.beverage = beverage;
   }

   public String getDescription() {
      return beverage.getDescription() + ", Vanilla";
   }

   public double cost() {
    	return 0.20 + beverage.cost();
   }
}

The main: Here we are creating a new Decaf object with no condiments and printing its description and cost. The second beverage is created and we call it DarkRoast, we add two shots of Mocha and a shot of vanilla. Note how the beverage object is passed into the condiment decorator, this is how wrapping works. DarkRoast is wrapped with Mocha first, the Mocha object stores a reference to the beverage passed in. This is demonstrated in Figure 1.

public class StarbuzzCoffee {
   public static void main(String args[]) {
      Beverage beverage = new Decaf();
      System.out.println(beverage.getDescription() + " $" + beverage.cost());

      Beverage beverage2 = new DarkRoast();
      beverage2 = new Mocha(beverage2);
      beverage2 = new Mocha(beverage2);
      beverage2 = new Vanilla(beverage2);
      System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
   }
}

Example

Java I/O classes

Java I/O API

JDK 6u1 Source under the JRL license

extract: java -jar jdk-6u1-ea-src-b03-jrl-19_jan_2007.jar

java.io location: j2se/src/share/classes/java/io/


Abstract Component class:

public abstract class InputStream {
   abstract int read();

}

Concrete Component class:

class FileInputStream extends InputStream
{
   public int read();
}

Decorator class:

public
class FilterInputStream extends InputStream {
{
   //Local variable for storing the reference of the concrete component class
   protected volatile InputStream in;

   protected FilterInputStream(InputStream in) {
      this.in = in;
   }
    
   public int read() {
      return in.read();
   }
}

Concrete Decorator class:

public
class PushbackInputStream extends FilterInputStream {  
   public PushbackInputStream(InputStream in, int size) {
       super(in); //Calls the FilterInputStream class and stores the reference of InputStream
       if (size <= 0) {
           throw new IllegalArgumentException("size <= 0");
       }
       this.buf = new byte[size];
       this.pos = size;
   }

   public int read(){
       ensureOpen();
       if (pos < buf.length) {
          return buf[pos++] & 0xff;
       }
       return super.read();
   }
}

MPCL

Multi purpose class library (MPCL)

Source Code

GPL license


Concrete Components
TDecoratorTableCellRenderer:

/**
*  Table cell renderer that  decorates a delegate cell renderer.  It implements
*  the Decorator design pattern.
*/
public class TDecoratorTableCellRenderer implements TableCellRenderer
{

 ///  Delegate table cell renderer.
 private TableCellRenderer   tDelegateTableCellRenderer;

 ///  Table cell decorator.
 private ITableCellDecorator   tTableCellDecorator;


 //
 //  C O N S T R U C T O R S
 //

 /**
 *  Builds a new instance.
 *  @param tDELEGATE_TABLE_CELL_RENDERER The delegate table cell renderer.
 *  @param tTABLE_CELL_DECORATOR         Table cell decorator.
 */
 public TDecoratorTableCellRenderer ( TableCellRenderer   tDELEGATE_TABLE_CELL_RENDERER ,
                                      ITableCellDecorator tTABLE_CELL_DECORATOR         )
 {
   tDelegateTableCellRenderer = tDELEGATE_TABLE_CELL_RENDERER;
   tTableCellDecorator        = tTABLE_CELL_DECORATOR;
 }


DefaultTableCellRenderer:

public class DefaultTableCellRenderer extends JLabel
   implements TableCellRenderer, Serializable
{
   /**
    * Creates a default table cell renderer.
    */
   public DefaultTableCellRenderer() {
       super();
       setOpaque(true);
       setBorder(getNoFocusBorder());
   }
}

TDateTableCellRenderer:

public class TDateTableCellRenderer extends DefaultTableCellRenderer
{
 ///  Date format.
 private DateFormat   tDateFormat;

 //
 //  C O N S T R U C T O R S
 //

 /**
 *  Sets the String object for the cell being rendered to value.
 *  @param tVALUE The value for this cell; if \a tVALUE is \a null it sets the
 *                the text value to an empty string.
 *  @see javax.swing.table.DefaultTableCellRenderer#setValue()
 */
 protected void setValue (Object tVALUE)
 {
   setText (( tVALUE == null ) ? "" : tDateFormat.format ((Date) tVALUE));
 }

TPopulationTableCellRenderer:

///  Table cell renderer for \a TPopulation objects.
public class TPopulationTableCellRenderer extends DefaultTableCellRenderer
{

 ///  Number format.
 public NumberFormat   tNumberFormat;

 //
 //  C O N S T R U C T O R S
 //

 /**
 *  Sets the String object for the cell being rendered to value.
 *  @param tVALUE The value for this cell; if \a tVALUE is \a null it sets the
 *                the text value to an empty string.
 *  @see javax.swing.table.DefaultTableCellRenderer#setValue()
 */
 protected void setValue (Object tVALUE)
 {
   if ( tNumberFormat == null)
   {
     tNumberFormat = TDecimalFormat._getPopulationInstance();
   }
   setText (( tVALUE == null ) ? "" : tNumberFormat.format (((TPopulation) tVALUE).intValue()));
 }

 //
 //  C O N S T R U C T O R S
 //

 ///  Builds a new instance.
 public TPopulationTableCellRenderer()
 {
   super();
   setHorizontalAlignment (DefaultTableCellRenderer.RIGHT);
 }

}  // class TPopulationTableCellRenderer

Contributions

Adapter pattern in Eclipse

References

Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2.

Freeman, Eric; Freeman, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Head First Design Patterns. O`Reilly. ISBN 0-596-00712-4.

http://www.dofactory.com/Patterns/PatternDecorator.aspx