Skip to content

Latest commit

 

History

History
180 lines (123 loc) · 7.37 KB

05.装饰器模式.md

File metadata and controls

180 lines (123 loc) · 7.37 KB

#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对象?

伪装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个方法,貌似很浪费。有没有更加灵活的方法呢: 不用新增那么多类,也不用重写覆盖多余的方法

优化装饰器

我们分析一下问题的原因:

  1. 增加的类过多:我们可以考虑使用匿名内部类实现。
  2. 重写覆盖了多余的方法,原因是我们实现的是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了,它也是使用装饰器模式实现的。

装饰器模式

装饰器模式: 在不修改原有代码的情况下,动态地给一个对象添加一些额外的职责

装饰器模式的特点:

  1. 装饰器和被装饰对象有相同的超类型。装饰器利用继承达到“类型匹配”,而不是利用继承获得“行为”,所得到的新行为,并不是继承自超类,而是由组合对象得来的(装饰器有一个实例变量以保存某个Component的引用)。换句话说:装饰器利用继承达到“类型匹配”,利用组合得到新的行为。
  2. 你可以用一个或多个装饰器包装一个组件,从而可以创造出很多不同行为的组合。
  3. 装饰器可以在被装饰对象的行为之前与/或之后,加上自己的行为,甚至将被装饰对象的行为整个取代掉,而达到特定的目的。
  4. 装饰器一般对组件的客户是透明的,除非客户程序依赖于具体的组件,而不是抽象的组件。

装饰器的结构图:

wrapper pattern

下面引用《重构与模式》中的一段话:

模式的结构图仅是一个例子而已,并不是规范,对于这一点,似乎怎么强调也不过分。结构图只是描述了我们最经常见到的实现。 因此它与自己的实现有很多的相同点,但是两者之间的区别也不可避免,实际上也是乐于见到的。最低限度,也应该将参于者改为适合具体领域吧。 当实现面临的权衡告诉发生改变时,实现就可以开始与结构图差异很大。