Understanding Decorator and chain of responsibility design patterns with Java examples
Some design patterns do have subtle differences, and it is important to understand the intent of a pattern. Decorator and chain of responsibility may look similar but the intent is different. Decorator has a subtle difference with a proxy design pattern.
Q. What is a decorator design pattern?
A. A decorator enhances the behavior of an object at run time. Decorators provide a flexible alternative to sub classing for extending functionality. Java I/O API is a good example of a decorator design pattern. If you were to implement this with an inheritance hierarchy (happens at compile time) you would have ended up with a fragile and large hierarchy. Instead, it was designed with decorator design pattern. You can have different combinations at run time.
//decorating at run time String inputText = "Some text to read"; ByteArrayInputStream bais = new ByteArrayInputStream(inputText.getBytes()); Reader isr = new InputStreamReader(bais); BufferedReader br = new BufferedReader(isr); br.readLine();
So, why favor composition over inheritance?
Q. What is a chain of responsibility design pattern?
A. Use the Chain of Responsibility pattern when you can conceptualize your program as a chain made up of links, where each link (or processor) can either handle a request/command or pass it down the chain.
Q. How does chain of responsibility differ from a decorator design pattern?
A. The fact that you can break the chain at any point and each chain can either handle or pass it differentiates the Chain of Responsibility pattern from the Decorator pattern. In other words, if you chain, each link will be called along the chain, and with a decorator, you're not guaranteed of this order, but you can be confident that additional responsibilities can be attached.
Chain link interface that passes the request or command object java.lang.Number
//Chain link interface public interface NumberHandlerChain { void setNextChain(NumberHandlerChain nextChain); void handleNumber(Number number); }
First chain that adds 1 if the command or request is an odd number
public class OddNumberHandler implements NumberHandlerChain { private NumberHandlerChain nextChain; @Override public void setNextChain(NumberHandlerChain nextChain) { this.nextChain = nextChain; } @Override public void handleNumber(Number number) { if (number.longValue() % 2 != 0) { number = number.longValue() + 1; System.out.println(this.getClass().getName() + " : " + number); } if (this.nextChain != null) { this.nextChain.handleNumber(number); } } }
Second chain that adds 2 if the command or request is an even number
public class EvenNumberHandler implements NumberHandlerChain { private NumberHandlerChain nextChain; @Override public void setNextChain(NumberHandlerChain nextChain) { this.nextChain = nextChain; } @Override public void handleNumber(Number number) { if (number.longValue() % 2 == 0) { number = number.longValue() + 2; System.out.println(this.getClass().getName() + " : " + number); } if(this.nextChain != null) { this.nextChain.handleNumber(number); } } }
Third chain that adds 5 if the command or request is a multiple of 5
public class MultipleOfFiveHandler implements NumberHandlerChain { private NumberHandlerChain nextChain; @Override public void setNextChain(NumberHandlerChain nextChain) { this.nextChain = nextChain; } @Override public void handleNumber(Number number) { if (number.longValue() % 5 == 0) { number = number.longValue() + 5; System.out.println(this.getClass().getName() + " : " + number); } if (this.nextChain != null) { this.nextChain.handleNumber(number); } } }
Sender who constructs the Request/Command objects which are java.lang.Number objects and pass them down the chain of processors (i.e implementation of NumberHandlerChain).
//Sender public class CorTest { private NumberHandlerChain chain; public static void main(String[] args) { CorTest sender = new CorTest(); NumberHandlerChain chain1 = new OddNumberHandler(); NumberHandlerChain chain2 = new EvenNumberHandler(); NumberHandlerChain chain3 = new MultipleOfFiveHandler(); sender.setChain(chain1); chain2.setNextChain(chain3); chain1.setNextChain(chain2); sender.getChain().handleNumber(7); //all 3 chains handles sender.getChain().handleNumber(4); //only EvenNumberHandler handles this } public NumberHandlerChain getChain() { return chain; } public void setChain(NumberHandlerChain chain) { this.chain = chain; } }
Output:
com.cor.OddNumberHandler : 8
com.cor.EvenNumberHandler : 10
com.cor.MultipleOfFiveHandler : 15
com.cor.EvenNumberHandler : 6
7 becomes 8 by adding 1, then 10 by adding 2, and then 15 by adding 5.
4 becomes 6 by adding 2.
So, order does matter.
Q. Can you think of practical examples of chain of responsibility design pattern?
Example 1: Java’s own Exception Handling mechanism because
1. Sender of an exception will not know which object in the chain will serve its request.
2. Each processor in the chain may decide to serve the exception by catching and logging or
3. wrapping it with an application specific exception and then rethrowing it to the caller or
4. don't handle it and leave it to the caller
Example 2: Servlet filters are implementation of the chain of responsibility pattern.
void doFilter(..) { // do things like security check, parsing payload, etc before the servlet gets called // invoke the servlet, or any other filters mapped to the target servlet chain.doFilter(..); // do stuff after the servlet finishes }
Example 3: An interceptor is generally a link in a chain of responsibility. Interceptors in Java frameworks like Spring MVC, Struts 2, etc intercept, handle or pass a message down the chain for other interceptors to handle.
More design patterns with Java examples. Will cover difference between decorator and proxy design pattern later.
Labels: design pattern
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home