设计模式 --- 模板模式

117 阅读3分钟

想必大家都用过 Servlet 来进行 Web 开发,我们需要继承 HttpServlet,并重写里面的 doGet 和 doPost 方法,其中的 doGet() 和 doPost() 方法就是模板方法(在父类中一般是抽象的),至于 doGet() 和 doPost() 方法哪里调用,如何调用,我们都不需要关心,这就是模板模式

复用和扩展


模板模式有两个作用,一个是复用,一个是扩展。假如下面是我自己写的基于 Servlet 的框架代码

public abstract class HttpServlet{
	// 该方法用final修饰,确保子类不能重写该方法
    public final service(HttpServletRequest req, HttpServletResponse resp){
    	String method = req.getMethod()
        
        if(method == "Post"){
        	doPost(req, resp);
        }else if(method == "GET"){
        	doGet(req, resp);        
        }else if(method == "PUT"){
        	doPut(req, resp);
        }else if(method == "DELETE"){
        	doDelete(req, resp);
        }
    
    }
    
    // 模板方法,交给子类实现
    protected abstract doGet(HttpServletRequest req, HttpServletResponse resp);
    protected abstract doPut(HttpServletRequest req, HttpServletResponse resp);
    protected abstract doDelete(HttpServletRequest req, HttpServletResponse resp);
    protected abstract doPost(HttpServletRequest req, HttpServletResponse resp);
}

该类是抽象类,service 方法是整个框架的核心逻辑,是框架的骨架,是一个模板,每个请求过来都要按照这个逻辑执行,这里就实现了父类方法的复用,而 doGet() 和 doPost() 这些方法是根据项目要求由程序员来自定义的,为整个框架提供了扩展点,实现了扩展的作用,父类的核心方法用 final 修饰,禁止子类进行重写,保证了逻辑的完整性

当然,这样子还存在一个问题,如果要对框架进行扩展,比如加上 doHeader() 相关逻辑,就要往抽象类中添加抽象方法,这就会导致所有子类也必须实现这个方法,这不符合开闭原则。解决这类问题的方法有很多,例如,在抽象类中不要把这些方法定义为抽象方法,而是给他们提供模式实现,即使是空的方法体也行,如下:

protected abstract doGet(HttpServletRequest req, HttpServletResponse resp){

}

或者直接抛出异常,禁止子类直接调用,但不强制子类重写

protected abstract doGet(HttpServletRequest req, HttpServletResponse resp){
	throw new UnsupportedOperationException();
}


同步回调函数


回调函数在很多地方都有应用,例如前端的 Promise 的 then() 和 catch() 都使用的是回调,同步回调和模板模式很像,都实现了代码的复用和扩展,让我们来看一下同步回调是如何实现的

// 回调接口
public interface CallBack{
	void then();
}

class A{
	public void sendAjax(CallBack callBack){
    	System.out.println("发送Ajax请求中");
        callBack.then();
    }
}

class B{
	public static void main(String args[]){
    	A a = new A();
        a.sendAjax(() -> {
            System.out.println("ajax 请求成功");
        });
    }
}

    可以看到,A 类中的 sendAjax() 方法定义了核心逻辑,也就是方法的骨架,模板,而其中的回调方法是可以自定义的,由外部传入具体的实现,由方法内部进行调用,和模板模式一样实现了复用和扩展。



模板模式和回调的区别


模板模式需要有个父类,在父类中定义方法的骨架,而回调则不需要定义父类方法,直接可以使用传入 lambad 表达式的方式来自定义内部的方法,更加的方便,也可以解决上面提到的模板模式中父类中扩展模板方法时子类必须进行重写的问题