1. Application Scenario:
1) We want to build an BBS and enable user to post their comments.
How can we filter the sensitive messages that user posted?
2) In chatting room, our message are filtered and tagged as " Davy Says: *** "
" Davy Says " has been added into message.
2. Traditional Approach:
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class MsgProcessor { private String msg; public String process() { // process html <> tag String result = msg.replaceAll("<", "{").replaceAll(">", "}"); // process sensitive words result = result.replaceAll("Sensitive", "Non-sensitive").replaceAll( "Bad", "Good"); return result; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import org.junit.Test; public class AppTest { private String message = "Sensitive, <script>, Hello World, Forbidden, Bad"; @Test public void test() { MsgProcessor processor = new MsgProcessor(); processor.setMsg(message); String result = processor.process(); System.out.println(result); } }
Non-sensitive, {script}, Hello World, Forbidden, Good
Comments:
1) As we can see, the processor is tightly coupled with the message itself and the rules.
2) Every time we want to add some new rules, we have to change the code inside the method process().
3) How can we use another more flexible and low-coupled approach to realize this?
--> We can make several filters, each filter focus on its specific rules.
--> Every time we want to add or delete some rules, we just add or delete the fiters.
3. Chain Of Responsibility Approach
1) Filter Interface
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public interface Filter { String doFiler(String msg); }
2) HtmlFilter
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class HtmlFilter implements Filter { public String doFiler(String msg) { return msg.replaceAll("<", "{").replaceAll(">", "}"); } }
3) SensitiveFilter
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class SensitiveFilter implements Filter { public String doFiler(String msg) { return msg.replaceAll("Sensitive", "Non-sensitive").replaceAll("Bad", "Good"); } }
4) MsgProcessor
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class MsgProcessor { private String msg; private List<Filter> filterList; public MsgProcessor() { filterList = new ArrayList<Filter>(); filterList.add(new HtmlFilter()); filterList.add(new SensitiveFilter()); } public MsgProcessor() { } public String process() { for (Filter filter : filterList) { msg = filter.doFiler(msg); } return msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
5) Test Case
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import org.junit.Test; public class AppTest { private String message = "Sensitive, <script>, Hello World, Forbidden, Bad"; @Test public void test() { MsgProcessor processor = new MsgProcessor(); processor.setMsg(message); String result = processor.process(); System.out.println(result); } }
6) Output
Non-sensitive, {script}, Hello World, Forbidden, Good
Comments:
1) Using List we can set the sequence of filter that take effect. That is easy to organize which filter execute first and which filter execute last.
2) Still there are further requirements:
1) If there is a filter-chain that already exists. And we want want to add this chain's function into our own filter-chain. How can we easily achieve this?
2) How can we easily conbine several chains together?
--> We can get all the filters that in previous filter-chain, and add these filers into our own filter-chain. But that is not convenience enough.
--> As we can see, a filter-chain performs the same function as a filter.
1) Added a new class named FilterChain
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import java.util.ArrayList; import java.util.List; public class FilterChain implements Filter { private List<Filter> filterList = new ArrayList<Filter>(); public String doFiler(String msg) { for (Filter filter : filterList) { msg = filter.doFiler(msg); } return msg; } public FilterChain addFilter(Filter filter) { filterList.add(filter); return this; } public void removeFilter(Filter filter) { filterList.remove(filter); } }
2) Test case as below
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import org.junit.Test; public class AppTest { private String message = "Sensitive, <script>, Hello World, Forbidden, Bad"; @Test public void test() { FilterChain filterChain = new FilterChain(); filterChain.addFilter(new HtmlFilter()) .addFilter(new SensitiveFilter()); message = filterChain.doFiler(message); System.out.println(message); } }
3) Output is still correct.
4. Still there are futher requirements:
1) We want bi-directional filter which can not only filter the request but also filter the response.
2) And the request/response message may not be simple String type.
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class Request { private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class Response { private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public interface Filter { void doFiler(Request request, Response response); }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class HtmlFilter implements Filter { public void doFiler(Request request, Response response) { request.setMessage(request.getMessage().replaceAll("<", "{") .replaceAll(">", "}")); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class SensitiveFilter implements Filter { public void doFiler(Request request, Response response) { request.setMessage(request.getMessage() .replaceAll("Sensitive", "Non-sensitive") .replaceAll("Bad", "Good")); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import java.util.ArrayList; import java.util.List; public class FilterChain implements Filter { private List<Filter> filterList = new ArrayList<Filter>(); public void doFiler(Request request, Response response) { for (Filter filter : filterList) { filter.doFiler(request, response); } } public FilterChain addFilter(Filter filter) { filterList.add(filter); return this; } public void removeFilter(Filter filter) { filterList.remove(filter); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import org.junit.Test; public class AppTest { private String requestMsg = "Sensitive, <script>, Hello World, Forbidden, Bad"; private String responseMsg = "Sensitive, <script>, Hello World, Forbidden, Bad"; @Test public void test() { FilterChain filterChain = new FilterChain(); filterChain.addFilter(new HtmlFilter()) .addFilter(new SensitiveFilter()); Request request = new Request(); request.setMessage(requestMsg); Response response = new Response(); response.setMessage(responseMsg); filterChain.doFiler(request, response); System.out.println(request.getMessage()); System.out.println(response.getMessage()); } }
Non-sensitive, {script}, Hello World, Forbidden, Good Sensitive, <script>, Hello World, Forbidden, Bad
Comments:
1) We only move one step futher which encapsulate the type of request and response.
2) We only process the request and still didn't make any procession to our response object.
3) Below is the final solution for this problem.
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public interface Filter { void doFiler(Request request, Response response, FilterChain filterChain); }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class HtmlFilter implements Filter { public void doFiler(Request request, Response response, FilterChain filterChain) { request.setMessage(request.getMessage().replaceAll("<", "{") .replaceAll(">", "}").concat("---Processed by HtmlFilter")); filterChain.doFiler(request, response, filterChain); response.setMessage(response.getMessage().replaceAll("<", "{") .replaceAll(">", "}").concat("---Processed by HtmlFilter")); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; public class SensitiveFilter implements Filter { public void doFiler(Request request, Response response, FilterChain filterChain) { request.setMessage(request.getMessage() .replaceAll("Sensitive", "Non-sensitive") .replaceAll("Bad", "Good") .concat("---Processed by SensitiveFilter")); filterChain.doFiler(request, response, filterChain); response.setMessage(response.getMessage() .replaceAll("Sensitive", "Non-sensitive") .replaceAll("Bad", "Good") .concat("---Processed by SensitiveFilter")); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import java.util.ArrayList; import java.util.List; public class FilterChain implements Filter { private List<Filter> filterList = new ArrayList<Filter>(); private int index = 0; public void doFiler(Request request, Response response, FilterChain filterChain) { if (index == filterList.size()) { return; } else { Filter filter = filterList.get(index); index++; filter.doFiler(request, response, filterChain); } } public FilterChain addFilter(Filter filter) { filterList.add(filter); return this; } public void removeFilter(Filter filter) { filterList.remove(filter); } }
package edu.xmu.designPattern.DesignPattern_ChaniOfResponsibility; import org.junit.Test; public class AppTest { private String requestMsg = "Request: Sensitive, <script>, Hello World, Forbidden, Bad"; private String responseMsg = "Response: Sensitive, <script>, Hello World, Forbidden, Bad"; @Test public void test() { FilterChain filterChain = new FilterChain(); filterChain.addFilter(new HtmlFilter()) .addFilter(new SensitiveFilter()); Request request = new Request(); request.setMessage(requestMsg); Response response = new Response(); response.setMessage(responseMsg); filterChain.doFiler(request, response, filterChain); System.out.println(request.getMessage()); System.out.println(response.getMessage()); } }
Request: Non-sensitive, {script}, Hello World, Forbidden, Good---Processed by HtmlFilter---Processed by SensitiveFilter Response: Non-sensitive, {script}, Hello World, Forbidden, Good---Processed by SensitiveFilter---Processed by HtmlFilter
Extension:
1) We can take a look at Servlet API. And we can find out interfaces
-> javax.servlet.FilterChain interface
-> javax.servlet.Filter interface
There is a limitation that wa cannot add a filterchain to another as Filter and FilterChain are two different interfaces.
This design is akward and can be improved as above.