Decorator Design Pattern in Java

SHARE & COMMENT :

Intent of Decorator Design Pattern:

  • Attach additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extending functionality.[GoF,p175]
  • Client specified embellishment of a core object by recursively wrapping it.
  • Wrapping a gift,putting it in a box and wrapping the box.

also read:

Problem:You want to add behavior or state to individual objects at runtime.Inheritance is not feasible because it is static and applies to an entire class.

Solution:Attach additional logic to an existing object.Use Delegation.Place an object in front of it that delegates calls to the original object and then makes needed changes before returning the value.This is a form of linked list.

Structural Summary:

  • Identify if the client would like to dynamically embellish a core object.
  • Start with a composite design and change the 1-to-many recursive relationship to 1-1.
  • Define each “embellishment” class a derived class of the class whose role has morphed from “Composite” to “Decorator”.
  • The client can now wrap a “core” object with any number of Decorator objects.Each decorator contributes its “embellishment” functionality and then delegates to its wrappee.

Decorator Design Pattern

Decorator Pattern involves encapsulating the original object inside an abstract wrapper interface.Both the decorator objects and the core object inherit from this abstract interface.The interface uses recursive composition to allow an unlimited number of decorator “layers” to be added to each core object.Note that this pattern allows responsibilities to be added to an object,not methods to an object’s interface.This interface presented to the client must remain constant as successive layers are specified.Also note that the core object identity has now been “hidden” inside of a decorator object.Trying to access core object is now a problem.

The Decorator attaches additional responsibilities to an object dynamically. The ornaments that are added to pine or fir trees are examples of Decorators. Lights, garland, candy canes, glass ornaments, etc., can be added to a tree to give it a festive look. The ornaments do not change the tree itself which is recognizable as a Christmas tree regardless of particular ornaments used. As an example of additional functionality, the addition of lights allows one to “light up” a Christmas tree.

Overview of Decorator Design Pattern

One class takes in another class both of which extend the same abstract class,and adds functionality.Decorator Pattern helps to add behavior or responsibilities to an object.This is also called “wrapper”.Java Design Patterns suggest that decorators should be abstract classes and the concrete implementation should be derived from them.Decorator and Adapter patterns are similar.Adapter also seems to decorate classes.The intent of using adapter is to convert the interface of one or more classes to suit the interface of the client program.In case of decorator,the intent is to add behavior and functionality to some of the objects,not all the objects or adding different functionalities to each of the objects.Can also be called as “Wrapper”.

Decorator Design Pattern ( Image Credit )

This pattern can also be used as a way to refactor a complex class into smaller pieces.Even if we donot need to attach responsibilities dynamically it can be clearer to have each responsibility in a different class.

Only disadvantage is code maintenance can be a problem as it provides the system with a lot of similar looking small objects(each decorator).

Difference that we see between a decorator pattern and subclassing is that we can decorate any class that implements an interface with a single class.Say we wanted to give ourselves a java.util.Map that printed a message whenever as added or removed a key.We may create a subclass of HashMap i.e PrintableMap and override put and remove methods.But if we wanted to create a printable version of TreeMap then we either create PrintableTreeMap which has almost identical code to PrintableMap?(which should itself become PrintingHashMap) or we create a Map decorator.

CheckList:

  • Ensure the context is: a single core(or non-optional) component,several optional wrappers and an interface that is commonto all.
  • Create a “Lowest Common Denominator” interface that makes all classes interchangeable.
  • Create a second level base class(Decorator) to support the optional wrapper classes.
  • Core class and Decorator class inherit from LCD interface.
  • Decorator class declares a composition relationship to the LCD interface,and this data member is initialized in its constructor.
  • Decorator class delegates to the LCD object.
  • Define a Decorator Derived class for each object embellishment.
  • Decorator derived classes implement their wrapper functinality and delegate to the Decorator base class.
  • 9) Client configures the type and ordering of Core and Decorator objects.

Rules of Thumb:
Adapter provides a different interface to its subject.Proxy provides the same interface.Decorator provides an enhanced interface.[GoF, p216]

Adapter changes the object’s interface,Decorator enhances an object’s responsibilities.Decorator is thus more transparent to the client.As a consequence,Decorator supports recursive composition,which isnt possible with pure Adapters.[GoF, p149]

Composite and Decorator have similar structure diagrams,reflecting the fact that both rely on recursive composition to organize an open-ended number of objects.[GoF, p219]

A Decorator can be viewed as a degenerate Composite with only one component.However a decorator adds additional responsibilities-it isnt intended for object aggregation.[GoF, p184]

Decorator is designed to let you add responsibilites to objects without subclassing.Composite’s focus is not an embellishment but on representation.These intents are distinct but complementary.Consequently Composite and Decorator are often used in concert. [GoF, p220]

Decorator lets us change the skin of an object.Strategy lets us change the guts.[GoF, p184]

Decorator and Proxy have diff purposes but similar structures.Both describe how to provide a level of indirection to another object,and the implementations keep a reference to the object to which they forward requests.[GoF, p220]

Below example gives one implementation of Decorator Pattern used in our project.Please note that we are using Apache Beehieve for our front-end application.

In our project we have a requirement to encrpt all the input data shown in jsp pages.Actually the idea and implementation was done by one developer named Sai Selvam in our project.Thankfully all of us learned from him.

Below are the steps:-

1) Create a class Crypto which extends from org.apache.beehive.netui.tags.html.Parameter class and overside setName and setValue methods and set the encrpyted names and values instead.

public class Crypto extends Parameter
  @Override
  public void setName(String arg0)
super.setName(CryptoUtil.encrypt(arg0));
@Override
public void setValue(Object arg0) throws JspException
 super.setValue(CryptoUtil.encrypt(String.valueOf(arg0)));
}

Please note that CryptoUtil is our project related class and we using BASE64Decoder and BASE64Encoder classes for achieving encryption and decryption which we will not be discussing in this article.Please refer manual (sun.misc.BASE64Decoder and sun.misc.BASE64Encoder) for further details.

2) Create a URLRewriteFilter class which acts as a filter i.e implements Filter interface.Cast the request object to CryptoWrappedRequest wrapper class which is extending from HttpServletRequestWrapper class.

public void doFilter(ServletRequest pRequest , ServletResponse pResponse,
 FilterChain pChain) throws IOException , ServletException {
 if (pRequest instanceof HttpServletRequest) {
 HttpServletRequest request = (HttpServletRequest) pRequest;
HttpServletResponse response = (HttpServletResponse) pResponse;
 CryptoWrappedRequest cryptoWrappedRequest = new CryptoWrappedRequest(request , response);
 cryptoWrappedRequest.getRequestDispatcher(request.getServletPath()).forward(cryptoWrappedRequest , response);
  if (!response.isCommitted()) {
 pChain.doFilter(request , response);}

3) Create CryptoWrappedRequest class which extends from HttpServletRequestWrapper class and provides additional functinalities.

import org.apache.beehive.netui.pageflow.PageFlowUtils;
import org.apache.beehive.netui.pageflow.internal.PageFlowRequestWrapper;
public class CryptoWrappedRequest extends HttpServletRequestWrapper {
private PageFlowRequestWrapper pageFlowRequestWrapper;
private String newQueryString = null;
private Map<String, String[]> params = null;
 public CryptoWrappedRequest(HttpServletRequest request , HttpServletResponse response) {
  super(request);
  pageFlowRequestWrapper = PageFlowRequestWrapper.wrapRequest(request);
 }

 @Override
 public String getQueryString() {
  if (newQueryString == null)
   constructQueryString(pageFlowRequestWrapper.getQueryString());
  return newQueryString;
 }

 // If the request has some query parameters appended to the URL then we decrypt key,value pairs and then
// append and form the newQueryString text approximately.If there is no data appended newQueryString value remains the same old value
//retrieved from pageFlowRequestWrapper.

 protected void constructQueryString(String encryptedQueryString) {
  if (encryptedQueryString != null &amp;amp;&amp;amp; encryptedQueryString.length() > 0) {
   for(String param : encryptedQueryString.split("&amp;amp;")) {
    String[] paramNameValuePair =  param.split("=");
    try {
     if(paramNameValuePair.length>1)
     {
      String paramName = URLDecoder.decode(paramNameValuePair[0] , "UTF-8");
      String paramValue = URLDecoder.decode(paramNameValuePair[1] , "UTF-8");
      addToQueryString(paramName , paramValue);
     }
    }
    catch (UnsupportedEncodingException e) {
     //e.printStackTrace();
    }
    catch (Exception ex) {
     //e.printStackTrace();
    }
   }
  } else {
   newQueryString = encryptedQueryString;
  }
 }

 // This Method tries to decrypt the key and value before forming the newQueryString value.
 protected void addToQueryString(String paramKey, String paramValue) {
  String pmKeyDecrypted = CryptoUtil.decrypt(paramKey);
  String pmValueDecrypted = CryptoUtil.decrypt(paramValue);

  if (pmKeyDecrypted == null) { pmKeyDecrypted = paramKey; }
  if (pmValueDecrypted == null) { pmValueDecrypted = paramValue; }

  if (newQueryString == null) { newQueryString = ""; }
  StringBuffer newQS = new StringBuffer(newQueryString);
  if (newQS.length() > 0) { newQS.append("&amp;amp;"); }
  newQS.append(pmKeyDecrypted);
  newQS.append("=");
  newQS.append(pmValueDecrypted);
  newQueryString = newQS.toString();
 }

 @Override
 public String getParameter(String paramName) {
  if (pageFlowRequestWrapper.getParameter(paramName) != null) {
   return pageFlowRequestWrapper.getParameter(paramName);
  }
  else if (pageFlowRequestWrapper.getParameter(CryptoUtil.encrypt(paramName)) != null) {
   return CryptoUtil.decrypt(pageFlowRequestWrapper.getParameter(CryptoUtil.encrypt(paramName)));
  }
  return null;
 }

 @Override
 public Enumeration getParameterNames() {
  List<String> resultList = new ArrayList<String>();
  Enumeration enn = pageFlowRequestWrapper.getParameterNames();
  while(enn.hasMoreElements()) {
   String paramName = (String)enn.nextElement();
   String newName = CryptoUtil.decrypt(paramName);
   if (newName != null) {
    resultList.add(newName);
   } else {
    resultList.add(paramName);
   }
  }
  return Collections.enumeration(resultList);
 }

 @Override
 public String[] getParameterValues(String paramName) {
  String[] result = null;
  List<String> resultList = new ArrayList<String>();

  String[] paramValues = pageFlowRequestWrapper.getParameterValues(paramName);
  if (paramValues == null || paramValues.length == 0)
   paramValues = pageFlowRequestWrapper.getParameterValues(CryptoUtil.encrypt(paramName));

  if (paramValues != null &amp;amp;&amp;amp; paramValues.length > 0) {
   for(String paramValue : paramValues) {
    String newValue = CryptoUtil.decrypt(paramValue);
    if (newValue != null)
     resultList.add(newValue);
    else
     resultList.add(paramValue);
   }
  }

  if (resultList.size() > 0) {
   result = new String[resultList.size()];
   resultList.toArray(result);
  }

  return result;
 }

 @Override
 public Map getParameterMap()
 {
  if (params == null)
   processParameters(pageFlowRequestWrapper.getParameterMap());

  return params;
 }

 protected void processParameters(Map pm) {
  //Map pm = pageFlowRequestWrapper.getParameterMap();
  if (pm != null) {
   params = new HashMap<String , String[]>();
   Iterator pmIter = pm.keySet().iterator();
   while(pmIter.hasNext()) {
    String pmKey = (String) pmIter.next();
    if (!pmKey.startsWith(PageFlowUtils.ACTION_OVERRIDE_PREFIX)) {
     String pmKeyDecrypted = CryptoUtil.decrypt(pmKey);
     if (pmKeyDecrypted == null) pmKeyDecrypted = pmKey;
     for(String pmValue : (String[]) pm.get(pmKey)) {
      String pmValueDecrypted = CryptoUtil.decrypt(pmValue);
      if (pmValueDecrypted == null) pmValueDecrypted = pmValue;
      addToParams(pmKeyDecrypted , pmValueDecrypted);
     }
    }
   }
  } else {
   params = null;
  }
 }

 protected void addToParams(String paramKey , String paramValue) {
  List<String> listObj = new ArrayList<String>();

  if (params.containsKey(paramKey)) {
   Iterator iter = Arrays.asList((String[]) params.get(paramKey)).iterator();
   while(iter.hasNext()) {
    listObj.add((String) iter.next());
   }
  }
  listObj.add(paramValue);
  String[] strArray = new String[listObj.size()];
  listObj.toArray(strArray);
  params.put(paramKey , strArray);
 }

}

4) Add entries in web.xml for Filter class and the Cryto tag which we defined in Step 1.Also create a .tld file refering to our cryto class.

5) Finally in jsp for any input parameter we will be sending we will write code similar to this.

<crypto:EncryptParamter name="somename" value="value"/>

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Trackbacks

  1. […] Decorator : This standard seeks to offer a flexible alternative to extension object dynamic new features, without the use of inheritance. An example is the use of the scroll bar that can be incorporated dynamically to a window, text box or web page. […]

Speak Your Mind

*

Close
Please support the site
By clicking any of these buttons you help our site to get better