如果你发现你有很多重复的代码,你可能会考虑用模板方法消除容易出错的重复代码。这里有一个例子:下面的两个类,完成了几乎相同的功能:

  1. 实例化并初始化一个Reader来读取CSV文件;
  2. 读取每一行并解析;
  3. 把每一行的字符填充到Product或Customer对象;
  4. 将每一个对象添加到Set里;
  5. 返回Set。

正如你看到的,只有有注释的地方是不一样的。其他所有步骤都是相同的。

ProductCsvReader.java

public class ProductCsvReader {

    Set<Product> getAll(File file) throws IOException {
Set<Product> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
//不同
Product product = new Product(Integer.parseInt(tokens[0]), tokens[1], new BigDecimal(tokens[2]));
returnSet.add(product);
line = reader.readLine();
}
}
return returnSet;
}
}

CustomerCsvReader.java

public class CustomerCsvReader {

    Set<Customer> getAll(File file) throws IOException {
Set<Customer> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
//不同
Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1], tokens[2], tokens[3]);
returnSet.add(customer);
line = reader.readLine();
}
}
return returnSet;
}
}

对于本例来说,只有两个实体,但是一个真正的系统可能有几十个实体,所以有很多重复易错的代码。你可能会发现Dao层有着相同的情况,在每个Dao进行增删改查的时候几乎都是相同的操作,唯一与不同的是实体和表。让我们重构这些烦人的代码吧。根据GoF设计模式第一部分提到的原则之一,我们应该“封装不同的概念“ProductCsvReader和CustomerCsvReader之间,不同的是有注释的代码。所以我们要做的是,把相同的放到一个类,不同的抽取到另一个类。我们先开始编写ProductCsvReader,我们使用Extract Method提取带注释的部分:

ProductCsvReader.java after Extract Method

public class ProductCsvReader {

    Set<Product> getAll(File file) throws IOException {
Set<Product> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
Product product = unmarshall(tokens);
returnSet.add(product);
line = reader.readLine();
}
}
return returnSet;
} Product unmarshall(String[] tokens) {
Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],
new BigDecimal(tokens[2]));
return product;
}
}

现在我们已经把相同(重复)的代码和不同(各自特有)的代码分开了,我们要创建一个父类AbstractCsvReader,它包括两个类(ProductReader和CustomerReader)相同的部分。我们把它定义为一个抽象类,因为我们不需要实例化它。然后我们将使用Pull Up Method重构这个父类。

AbstractCsvReader.java

abstract class AbstractCsvReader {

    Set<Product> getAll(File file) throws IOException {
Set<Product> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
Product product = unmarshall(tokens);
returnSet.add(product);
line = reader.readLine();
}
}
return returnSet;
}
}

ProductCsvReader.java after Pull Up Method

public class ProductCsvReader extends AbstractCsvReader {

    Product unmarshall(String[] tokens) {
Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],
new BigDecimal(tokens[2]));
return product;
}
}

如果在子类中没有‘unmarshall’方法,该类就无法进行编译(它调用unmarshall方法),所以我们要创建一个叫unmarshall的抽象方法。

AbstractCsvReader.java with abstract unmarshall method

abstract class AbstractCsvReader {

    Set<Product> getAll(File file) throws IOException {
Set<Product> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
Product product = unmarshall(tokens);
returnSet.add(product);
line = reader.readLine();
}
}
return returnSet;
} abstract Product unmarshall(String[] tokens);
}

现在,在这一点上,AbstractCsvReader是ProductCsvReader的父类,但不是CustomerCsvReader的父类。如果CustomerCsvReader继承AbstractCsvReader编译会报错。为了解决这个问题我们使用泛型。

AbstractCsvReader.java with Generics

abstract class AbstractCsvReader<T> {

    Set<T> getAll(File file) throws IOException {
Set<T> returnSet = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))){
String line = reader.readLine();
while (line != null && !line.trim().equals("")) {
String[] tokens = line.split("\\s*,\\s*");
T element = unmarshall(tokens);
returnSet.add(product);
line = reader.readLine();
}
}
return returnSet;
} abstract T unmarshall(String[] tokens);
}

ProductCsvReader.java with Generics

public class ProductCsvReader extends AbstractCsvReader<Product> {

    @Override
Product unmarshall(String[] tokens) {
Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],
new BigDecimal(tokens[2]));
return product;
}
}

CustomerCsvReader.java with Generics

public class CustomerCsvReader extends AbstractCsvReader<Customer> {

    @Override
Customer unmarshall(String[] tokens) {
Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1],
tokens[2], tokens[3]);
return customer;
}
}

这就是我们要的!不再有重复的代码!父类中的方法是“模板”,它包含这不变的代码。那些变化的东西作为抽象方法,在子类中实现。记住,当你重构的时候,你应该有自动化的单元测试来保证你不会破坏你的代码。我使用JUnit,你可以使用我帖在这里的代码,也可以在这个Github库找一些其他设计模式的例子。在结束之前,我想说一下模板方法的缺点。模板方法依赖于继承,患有 the Fragile Base Class Problem。简单的说就是,修改父类会对继承它的子类造成意想不到的不良影响。事实上,基础设计原则之一的GoF设计模式提倡“多用组合少用继承”,并且许多其他设计模式也告诉你如何避免代码重复,同时又让复杂或容易出错的代码尽量少的依赖继承。欢迎交流,以便我可以提高我的博客质量。

原文地址;Template Method Pattern Example Using Java Generics

翻译的不好,欢迎拍砖!


菜鸟译文(二)——使用Java泛型构造模板方法模式的更多相关文章

  1. 折腾Java设计模式之模板方法模式

    博客原文地址:折腾Java设计模式之模板方法模式 模板方法模式 Define the skeleton of an algorithm in an operation, deferring some ...

  2. Java基础系列二:Java泛型

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 一.泛型概述 1.定 ...

  3. Java设计模式之模板方法模式(Template Method)

    一.含义 定义一个算法中的操作框架,而将一些步骤延迟到子类中.使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤,不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现. 二 ...

  4. Java抽象类应用—模板方法模式

    模板方法模式(Templete method) 定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中,模板方法模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤. 例: ...

  5. Java设计模式应用——模板方法模式

    所谓模板方法模式,就是在一组方法结构一致,只有部分逻辑不一样时,使用抽象类制作一个逻辑模板,具体是实现类仅仅实现特殊逻辑就行了.类似科举制度八股文,文章结构相同,仅仅具体语句有差异,我们只需要按照八股 ...

  6. Java设计模式之模板方法模式(Template)

    前言: 我们在开发中有很多固定的流程,这些流程有很多步凑是固定的,比如JDBC中获取连接,关闭连接这些流程是固定不变的,变动的只有设置参数,解析结果集这些是根据不同的实体对象“来做调整”,针对这种拥有 ...

  7. Java/C++实现模板方法模式---数据库操作

    对数据库的操作一般包括连接.打开.使用.关闭等步骤,在数据库操作模板类中我们定义了connDB().openDB().useDB().closeDB()四个方法分别对应这四个步骤.对于不同类型的数据库 ...

  8. java设计模式之模板方法模式

    模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中. 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.通俗的说的就是有很多相同的步骤的,在某一些地方可能有一些差 ...

  9. 从西天取经的九九八十一难来看Java设计模式:模板方法模式

    目录 示例 模板方法模式 定义 意图 主要解决问题 适用场景 优缺点 西天取经的九九八十一难 示例 当我们设计一个类时,我们能明确它对外提供的某个方法的内部执行步骤, 但一些步骤,不同的子类有不同的行 ...

随机推荐

  1. Excel之定位和查找

    在数据量比较少的情况下,我们要到达Excel中某一位置时,通常会用鼠标拖动滚动条到达需要的位置,查找某已知固定的值,用Ctr+F,在查找内容中输入对应的值即可一个个的查找到其对应的位置.但当数据量较多 ...

  2. Linux下RocksDB、LevelDB、ForestDB性能测试对比

    简要说明 本次环境与http://www.cnblogs.com/oloroso/p/6306352.html中的一致. 依然是增删查改各测试10000次,每个测试重复5次取平均值. 1.不使用jem ...

  3. LevelDB和ForestDB简单性能测试(含代码)

    测试环境简单说明 Windows下测试 硬件环境如下: 处理器:Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz 内 存:8GB 硬 盘:希捷 ST1000DM003 操 ...

  4. 51单片机和Arduino—闪烁灯实现

        技术:51单片机学习.Keil4环境安装.Arduino环境安装.闪烁灯教程   概述 本文提供51单片机.Arduino单片机入门软件安装和一些需要使用的软件介绍,为后续单片机.嵌入式开发做 ...

  5. 【Android开发经验】怎样查看android-support-v4支持包中的源代码

    在support-v4包里面.加入了非常多的支持控件,比方ViewPager,Fragment等,为了解决一些问题,我们有时候想要看一下实现源代码,可是点进去之后.源代码并不会显示出来,会出现以下的情 ...

  6. 【WEB2.0】 网页调用QQ聊天(PC+M站)

    很多时候,我们在网站中需要加入联系QQ的功能,我下面就来说下在web页面中调用QQ聊天是如何实现的,直接上代码: <!DOCTYPE HTML> <html> <head ...

  7. 快排法求第k大

    快排法求第k大,复杂度为O(n) import com.sun.media.sound.SoftTuning; import java.util.Arrays; import java.util.Ra ...

  8. 如何禁用Visual Studio 2013的浏览器链接功能

    VS2013新增的Browser Link功能虽然“强大”,但我并不需要. 但默认是开启的,会在页面中自动添加如下的代码,真是烦人! <!-- Visual Studio Browser Lin ...

  9. ASP.NET Core之项目文件简介及配置文件与IOC的使用

    原文地址:https://www.cnblogs.com/knowledgesea/p/7079880.html 序言 在当前编程语言蓬勃发展与竞争的时期,对于我们.net从业者来说,.Net Cor ...

  10. mvc 缓存页面 减轻服务器压力

    方法: using System; using System.Collections.Generic; using System.Linq; using System.Web; using Syste ...