若是你发现你有不少重复的代码,你可能会考虑用模板方法消除容易出错的重复代码。这里有一个例子:下面的两个类,完成了几乎相同的功能:
html
正如你看到的,只有有注释的地方是不同的。其余全部步骤都是相同的。
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; } }
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提取带注释的部分:
git
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重构这个父类。
github
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; } }
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的抽象方法。
设计模式
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编译会报错。为了解决这个问题咱们使用泛型。
ide
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); }
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; } }
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 测试
翻译的很差,欢迎拍砖! spa