模板方法模式的介绍
模板方法的定义:
定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类在不改变一个算法的结构即可重新定义该算法的某些特定步骤。
我们考虑这么一种场景,假设我们需要读取文件,然后将读取出来的数据转换成实体类并存入数据库,然而文件的格式是不同的,有如下:
binga@18@Beijing@xxxx@163.com
xiaming@20|Shanghai@yyyy@163.com
xiahong@16@Guangzhou@zzz@163.com
各个字段以“@”分隔,也有式如下:
binga|18|Beijing|xxxx@163.com
xiaming|20|Shanghai|yyyy@163.com
xiahong|16|Guangzhou|zzz@163.com
各个字段以“|”分隔。当然还有文件开始不是数据的格式,而是开始说明的,如下:
data begin this is not available
binga@18@Beijing@xxxx@163.com
xiaming@20|Shanghai@yyyy@163.com
xiahong@16@Guangzhou@zzz@163.com
我们来分析一下整个需求中,我们可以定义一个类然后根据文件路径打开输出流,然后一行一行的读取数据、判断解析并入库,但是文件的形式是变化的,我们需要在代码中通过各种的if else进行判断,判断各个文件的格式,随着文件格式的增加我们需要在代码中添加if else判断,很明显这违反了“开闭”原则。需要注意的是文件的解析入库中流程是不变的,通过读取文件、解析和入库,但是变化的是文件的解析流程,需要各种数据的分隔符以及是否是有效的行数据,那么我们可以将流程抽象出来,如下:
其中persisit我们可以编写整个流程,如下:
void persist(String fileName) {
File file = new File(fileName);
if (!file.exists()) {
throw new RuntimeException("file" + fileName + "not esists");
}
String line = null;
int lineNum = 0;
try(BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
while ((line = br.readLine()) != null) {
lineNum++;
if (!isAvailable(lineNum)) continue;
// 解析行数据
Dto dto = parseLine(line);
// 保存数据
save(dto);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
模板方法模式的使用场景:
- 一次实现算法的不变部分,并将可变的部分留给子类来实现。
- 各个子类中公共的类型被提取出来并集中到一个公共父类中以避免代码重复。
- 控制子类扩展。模板方法只在特定点调用钩子操作,这样只允许在这些点进行扩展。
模板方法的结构:
模板方法示例
我们还是以上面的文件解析为示例,首先给出Dto对象结构:
class Dto {
private String name;
private int age;
private String address;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
抽象类如下:
abstract class FilePersistence {
void persist(String fileName) {
File file = new File(fileName);
if (!file.exists()) {
throw new RuntimeException("file" + fileName + "not esists");
}
String line = null;
int lineNum = 0;
try(BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
while ((line = br.readLine()) != null) {
lineNum++;
if (!isAvailable(lineNum)) continue;
// 解析行数据
Dto dto = parseLine(line);
// 保存数据
save(dto);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
public abstract boolean isAvailable(int lineNum);
public abstract Dto parseLine(String line);
public abstract void save(Dto dto);
}
那么我们来实现一个解析如下格式的类:
data begin this is not available
.....
......
binga@18@Beijing@xxxx@163.com
xiaming@20|Shanghai@yyyy@163.com
xiahong@16@Guangzhou@zzz@163.com
实现类如下:
interface DtoDao {
int saveDto(Dto dto);
}
class ConcreteFilePersistence extends FilePersistence {
private DtoDao dtoDao;
@Override
public boolean isAvailable(int lineNum) {
// 前三行的数据时无效数据,不需要解析
if (lineNum <= 3) return false;
return true;
}
@Override
public Dto parseLine(String line) {
String[] columns = line.split("@", -1);
Dto dto = new Dto();
dto.setName(columns[0]);
dto.setAge(Integer.parseInt(columns[1]));
dto.setAddress(columns[2]);
dto.setEmail(columns[3]);
return dto;
}
@Override
public void save(Dto dto) {
// 这里是存储到数据库,也可以存储到消息的队列等,redis缓存
dtoDao.saveDto(dto);
}
}
模板方法模式的优缺点
优点:
- 提高代码复用性。将相同部分的代码放在抽象的父类中。
- 提高了拓展性。将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为。
- 实现了反向控制。通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制,符合“开闭原则”。
缺点:
- 引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。
模板方法模式在源码中的使用
在Servlet中HttpServlet就是使用的模板方法,其中service方法就已经将流程定义好,而具体的请求方法,如get、post和head等则交给其子类来实现,如下:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
而其定义的doGet、doPost、doHead、doPut和doDelete方法则提供了默认的实现:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
this.doGet(req, resp);
} else {
NoBodyResponse response = new NoBodyResponse(resp);
this.doGet(req, response);
response.setContentLength();
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
当然,这些方法需要子类来重写从而完成功能。