设计模式之模板模式
顾名思义,定义一个模板,使用模板的子类可以往模板里面填写不同内容,但是模板的结构不能改变。
同样的场景,有发送短信,发送邮件,发送后都需要记录日志,发送前还需要进行内容校验,防止有一些不法信息。
用模板模式,就需要提取出发短信和发邮件两个操作的共同点,有两个,第一个是内容拦截,第二个是记录日志,
而不同点是发送的执行,这需要让他们自己去做。
定义出一个如上图所示的UML图,SenderService是一个抽象类,其中有模板方法。
抽象方法send()是让子类自己实现,log()方法可以实现也不可实现。
interceptor()方法是校验非法内容的,不容其他子类修改。
这个模板类最终代码如下:
/**
* @author lw
* @date 2022/3/30 0030
* @description 消息模板类
*/
public abstract class SenderService {
/**
* 提供一个钩子函数,让子类可以自己实现自己的日志
* 否则就使用默认日志记录方式
*/
public void log(){
System.out.println("SenderService.log");
}
/**
* 信息拦截
* @param content
* @return
*/
private String interceptor(String content){
if("不法信息".equals(content)){
content = "*****";
}
return content;
}
/**
* 消息发送 由具体执行的类来决定
* @param args
*/
protected abstract void send(String ...args);
/**
* 模板方法 模板方法不能被子类改写,所以需要定义为final
* @param args
*/
public final void sendMsg(String ...args){
String content = this.interceptor(args[1]);
args[1] = content;
this.send(args);
this.log();
}
}
最主要的是sendMsg()这个模板方法,里面定义了操作顺序,外界就通过这个方法来发送消息。
调用者
String toPhone = "12306";
String content = "hello world";
SenderService smsService = new SmsService();
smsService.sendMsg(toPhone,content);
SenderService mailService = new MailService();
mailService.sendMsg(toPhone,"不法信息");
//输出如下
短信发送:hello world
SmsService.log
邮件发送:*****
SenderService.log
定义一个接口,有多个实现类,这是模板模式么?
其实一般不算的,模板模式里面有个模板方法,这里面有方法调用顺序和结构,接口里面一般没有的。
除非这个接口里用了java8的新特性,如下代码所示,接口里面还有默认的方法。
/**
* @author lw
* @date 2022/3/30 0030
* @description
*/
public interface SenderService {
void send(String ...args);
//模板方法
default void sendMsg(String ...args){
this.intercept();
this.send(args);
this.log();
}
default void log(){
System.out.println("SenderService.log");
}
default void intercept(){
System.out.println("SenderService.intercept");
}
}
这个勉强算是用了模板模式,sendMsg()也勉强算是模板方法。
为何说勉强,sendMsg()是可以被子类实现,并改变顺序的。
public class SmsService implements SenderService{
@Override
public void send(String ...args) {
System.out.println("短信发送:"+args[0]);
}
@Override
public void sendMsg(String... args) {
this.send(args);
}
}
SenderService senderService = new SmsService();
senderService.sendMsg("hello world");
//实现了sendMsg后的 输出
短信发送:hello world
//没有实现sendMsg后的输出
SenderService.intercept
短信发送:hello world
SenderService.log
子类改变了模板方法的结构和执行逻辑。
个人感悟
说说模板模式的使用,模板模式因为定义了执行逻辑,这就像一个流程一样,如果流程是多变,不稳定的,那么应该考虑怎么使用模板模型。
比如,定义了拦截内容–>发送短信—>记录日志。突然改为了记录日志–>拦截内容–>记录日志–>发送短信—>记录日志。
如果是单独写在SmsService和MailService内的,那么就需要改两个类。
而如果流程改变是不同的,比如SmsService中是 记录日志–>拦截内容–>记录日志–>发送短信—>记录日志。
而MailService中是 拦截内容–>发送短信—>记录日志。
假如记录日志是log()方法,那么MailService中如下实现为空,则后续日志也记录不了,那么就需要拆分两个日志记录方式。
public void log(){
}
//拆分日志
public void logBeforeSend(){
}
public void logAfterSend(){
}
这种对于流程不统一的,设置流程单独变化的,要么使用可以修改模板方法的方式(不加final),要么还是单独拆开吧。
这是当初血一样的教训,好不容易改成了模板模式,结果其中一个类的流程改变了,就要单独拆分出来。
总结
模板模式需要定义模板类和模板方法,后续子类实现模板中对应的内容。