#05.装饰器模式
学习的最好方法就是实践,想当年大学学《设计模式》这门课的时候,重修两次,补考还挂了两次,最后还是甩小聪明才拿到的毕业证。 往事不多提,甚是可悲。
最近遇到一个问题,这里有一个controller的方法,方法里面需要获取request header的 x-forwarded-for 属性的值:
@RequestMapping(value = "/")
public void method(HttpServletRequest request,HttpServletResponse response) {
String remoteAddr = request.getHeader("x-forwarded-for");
}
问题是,在不修改现有代码的情况下,如何修改request.getHeader("x-forwarded-for")这个方法的返回值,即在当x-forward-for为空的情况下,指定一个默认值。
自然而然地,我们会想到使用filter来改变 request.getHeader("x-forward-for") 的行为。于是乎:
@WebFilter("/*")
public class MyFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 代码实现
chain.doFilter(request, response);
}
// 省略....
}
写到这里有点蒙了,request并不提供修改header的方法(也就是request.setHeader(key,value)),那怎么修改controller的getHeader方法的返回值呢?
或者我们可以换一种思路,chain.doFilter(request, response)
会往下传递request和response,我们能不能伪装一个request,从而改变其行为呢。
那问题就变得简单了:如何伪装这个request对象?
重新写一个类(称为wrapper)实现HttpServletRequest接口,并实现其所有方法,由于wrapper并不知道request的行为,所以使用构造方法将真实的request传入,在具体实现的时候,调用真实的request实现。
public class MyHttpServletRequestWrapper implements HttpServletRequest {
private HttpServletRequest request;
public MyHttpServletRequestWrapper(ServletRequest request) {
this.request = (HttpServletRequest) request;
}
@Override
public AsyncContext getAsyncContext() {
return this.getAsyncContext();
}
@Override
public String getHeader(String key) {
// 如果x-forwarded-for为空,则指定默认值
if(key.equals("x-forwarded-for") && this.getHeader == null) {
return "172.16.1.1";
}
return this.getHeader(key);
}
// ... 省略其他方法的实现
}
由于重新实现了getHeader(String key)的实现,在原来的基础上包装了一下,从而改变了getHeader的行为。我们我们在MyFilte就可以这样使用了:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(request); // 装饰request
chain.doFilter(requestWrapper, response); // 传递的是包装后的request
}
以上的代码可以按照我们的设想正确地执行,这就是 装饰器模式 。
##该死的需求
之前已经实现了修改getHeader("x-forwded-for")的行为,现在改一下需求,要求增加一个paramater参数[csrftoken],而且这个实现跟之前的不互相影响。 ok,重新下类,实现HttpServletRequest的所有方法,然后修改getParamater方法:
@Override
public String getParameter(String name) {
// 如果x-forwarded-for为空,则指定默认值
if(name.equals("csrftoken")) {
return "123456789";
}
return this.getParameter(key);
}
然后再增加一个header参数、或者在某个方法执行前后加入一些代码......
问题出来了,为了修改目标对象的行为,每次我们都需要继承他,实现他的M-1个方法,再去修改他的一个目标方法,貌似,为了实现修改某个方法的行为, 从而增加N个类,然后重写他的M-1个方法,貌似很浪费。有没有更加灵活的方法呢: 不用新增那么多类,也不用重写覆盖多余的方法 。
我们分析一下问题的原因:
- 增加的类过多:我们可以考虑使用匿名内部类实现。
- 重写覆盖了多余的方法,原因是我们实现的是Interface接口,而Interface里所有的方法都必须实现: 那么我们可以考虑使用一个中间类来实现接口,然后我们再继承中间类,从而也可以按需覆盖目标方法了。
public class MyHttpServletRequestWrapper implements HttpServletRequest {
private HttpServletRequest request;
public MyHttpServletRequestWrapper(HttpServletRequest request) {
this.request = request;
}
@Override
public Object getAttribute(String name) {
return this.request.getAttribute(name);
}
// 省略其他方法的实现
}
然后我们使用的时候,可以使用匿名内部类,按需重重写要的方法,例如入增加csrftoken参数:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(request){
@Override
public String getParameter(String name) {
if(name.equals("csrftoken")) {
return "123456789";
}
return this.getParameter(name);
}
};
chain.doFilter(requestWrapper, response); // 传递的是包装后的request
}
这样我们的装饰器就大功告成了!不过子啊实际的开发过程中,我们没有必要自己实现这个wrapper,servlet已经为我们提供了一个HttpServletRequest了,它也是使用装饰器模式实现的。
装饰器模式: 在不修改原有代码的情况下,动态地给一个对象添加一些额外的职责 。
装饰器模式的特点:
- 装饰器和被装饰对象有相同的超类型。装饰器利用继承达到“类型匹配”,而不是利用继承获得“行为”,所得到的新行为,并不是继承自超类,而是由组合对象得来的(装饰器有一个实例变量以保存某个Component的引用)。换句话说:装饰器利用继承达到“类型匹配”,利用组合得到新的行为。
- 你可以用一个或多个装饰器包装一个组件,从而可以创造出很多不同行为的组合。
- 装饰器可以在被装饰对象的行为之前与/或之后,加上自己的行为,甚至将被装饰对象的行为整个取代掉,而达到特定的目的。
- 装饰器一般对组件的客户是透明的,除非客户程序依赖于具体的组件,而不是抽象的组件。
装饰器的结构图:
下面引用《重构与模式》中的一段话:
模式的结构图仅是一个例子而已,并不是规范,对于这一点,似乎怎么强调也不过分。结构图只是描述了我们最经常见到的实现。 因此它与自己的实现有很多的相同点,但是两者之间的区别也不可避免,实际上也是乐于见到的。最低限度,也应该将参于者改为适合具体领域吧。 当实现面临的权衡告诉发生改变时,实现就可以开始与结构图差异很大。