日常中碰到的设计模式——模板模式
一、引言
设计模式,对于我们平时写代码而言,似乎离得很近(比如Spring里用到的工厂模式、单例模式这些),但需要自己去写的设计模式又几乎没有,毕竟框架里轮子都造好了。而我平时用到的也仅有策略模式,就是整个枚举,然后根据传进来的参数不同,看看在switch下匹配到哪个,然后去执行相应的方法,算是比较简单且常用的设计模式了。
二、模板模式简介
所谓的模板模式,也就是写一个抽象父类,这个父类公开定义了执行它的方法的方式/模板。然后它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
说白了就是,需要某些类需要执行相似的操作,这时候可以整个抽象类作为它们的父类,然后在不同的业务场景下去调子类从父类中重写的方法就可以了。这么讲似乎还是有点绕,下面举个例子吧:
假设有抽象父类A作为模板
public abstract class A {
public abstract String aa();
}
子类AImpl集成了这个模板,并重写了它的方法
public class AImpl extends A {
@Override
public String aa() {
return "AImpl";
}
}
那么现在来调用下
public static void main(String[] args) {
A a = new AImpl();
String aa = a.aa();
System.out.println(aa);
}
这个输出应该毫无悬念,是:
AImpl
三、实际应用
以上是个简单的模板模式的应用,看起来毫无技术含量,似乎平时也用不上的样子。下面将举个我最近实际中遇到的场景:
1、业务场景
因为项目需要对客户的一些数据做同步,比如:用户信息,项目信息,楼栋信息之类的。通过HTTP接口接受客户传来的JSON数据,然后传进数据库里。数据类似这样:
{
"name":"user",
"value":"\"LoginName\":\"aaa\",\"Password\":\"66f0429cac5a857beb41f8f091ac4c2e\""
}
name的值是需要同步到的表名,value的值则是需要同步的内容。看到这里大概思路应该都有了,先做个枚举,然后用策略模式,根据传来的不同的表名去做判断。然后,根据不同的策略去执行相应的操作。但是,这里发现了一个问题——无论是什么表,我们要对它做的操作都是:
1. 查,查看这个数据在表中存不存在;
2. 增,数据不存在就去新增;
3. 改,数据存在就去修改。
这些操作都是一样的,代码要是这么反反复复的写,要是这个要同步的表有很多,那不是要写吐了吗?
作为一个脱离了低级趣味的程序员,对于代码基本的审美还是要有的。对于这些反反复复,长得又差不多的操作,我们必须把他抽象出来,这时候,就需要模板模式登场了。。。
2、建立模板
首先,我们得先明确下,有哪些类需要去整个模板出来?这个其实看下增查改这几个步骤涉及了哪些类就知道了:
1. 实体类。JSON数据传进来后,对应于各个表的不同字段,就需要去建对应的实体,在数据库的增查改操作中也涉及到这些实体类,所以这里需要去做个实体类的模板;
2. dao接口。毕竟是要去操作数据库的,少了dao可不行,而且,对应于不同的表,也有不同的dao,所以这里需要做一个dao接口的模板。
接下来看下具体的操作(因为涉及项目的信息,所以这里实体类我把它简化了):
这里实体类举User作为例子,其他几个类似,就不写了:
public class User{
//用户ID
private String userId;
//用户登陆名
private String loginname;
//用户登陆密码
private String password;
public String getUserId() {
return userid;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public String setPassword(String password) {
this.password = password;
}
}
现在来写模板类,因为在查的时候需要实体类某个关键字作为筛选条件来查(譬如说ID),所以这里模板类先给定一个方法用来获取筛选条件的关键字(若有其他需要,再添加方法),如下:
public abstract class TemplateClass {
public abstract String getClassKey();
}
这样,一个实体类模板就完成了,下面来继承它:
public class User extends TemplateClass{
//用户ID
private String userId;
//用户登陆名
private String loginname;
//用户登陆密码
private String password;
public String getUserId() {
return userid;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public String setPassword(String password) {
this.password = password;
}
@Override
public String getClassKey() {
return userid;
}
}
那么要获取这个查询条件,通过去调用这个getClassKey方法就可以了,接下来看下增和改,对于实体类模板这儿,应该不需要再添加额外的方法了,相对于需要传查询条件的查方法而言,这两个操作其实都是传实体类进去,然后去执行相应的sql,不需要去获取实体类的其他信息了。
那么下面就需要来编写dao的模板了,我们先看下User的dao接口:
public interface UserDao{
/**
* 通过ID查询单条数据
*/
User query(String userId);
/**
* 新增数据
*/
int insert(User user);
/**
* 修改数据
*/
int update(User User);
}
因为模板的dao接口需要UserDao去重写到它所有的方法,所以它可以这么写:
public interface TemplateDao{
/**
* 通过ID查询单条数据
*/
TemplateClass query(String id);
/**
* 新增数据
*/
int insert(TemplateClass templateClass);
/**
* 修改数据
*/
int update(TemplateClass templateClass);
}
既然是模板dao,那么它里面涉及到的实体类也必须是模板实体类才行,接下来让UserDao去继承模板dao:
public interface UserDao extends TemplateDao{
/**
* 通过ID查询单条数据
*/
@Override
User query(String userid);
/**
* 新增数据
*/
@Override
int insert(TemplateClass user);
/**
* 修改数据
*/
@Override
int update(TemplateClass User);
}
这里要注意,这几个重写的方法入参类型必须与父类一致才行,返回的类型可以是父类里定义的返回类型的子类(这个一般IDE里也会提示的)。
3、模板方法
好了模板定义好了,可以开始整去调用这些模板的方法了,方法如下:
//这里入参得是模板类,那么调用模板方法时,根据不同的场景,传相应的继承模板类的实体类进来就可以了,如果这里User类的作为入参,templateClass相当于user,templateDao相当于userDao
public void templateMethod(RecieveRequestParam recieveRequestParam,TemplateClass templateClass, TemplateDao templateDao){
//解析JSON成相应的实体,如果这里User类的作为入参,则会去解析成User类实例
templateClass = JSONObject.parseObject(recieveRequestParam.getValue(), templateClass.getClass());
//获取关键字用于查询,如果这里User类的作为入参,会去获取userId来查
String key = templateClass.getClassKey();
//这里其实相当于user = userDao.query(userId);
TemplateClass template = templateDao.query(key);
if(null == template) {
//这里相当于userDao.insert(user);
templateDao.insert(templateClass);
}else {
//这里相当于userDao.udpate(user);
templateDao.update(templateClass);
}
}
然后去调用它:
switch (enum){
case USER:
templateMethod(recieveRequestParam,resultValue,new User(),userDao);
break;
case PROJECT:
templateMethod(recieveRequestParam,resultValue,new Position(),positionDao);
break;
case BUILDING:
templateMethod(recieveRequestParam,resultValue,new Building(),buildingDao);
break;
default:
break;
}
看到这里,想必模板模式如何使用已经很清晰了。后续有碰到其他设计模式的使用场景,再来记录一波。
参考内容来源: