使用Java泛型构造模板方法模式

来源:互联网 发布:免费流程审批软件 编辑:程序博客网 时间:2024/06/18 13:50

原文地址:http://blog.csdn.net/liushuijinger/article/details/38682999


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

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


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


ProductCsvReader.java

[java] view plaincopy
  1. public class ProductCsvReader {  
  2.    
  3.     Set<Product> getAll(File file) throws IOException {  
  4.         Set<Product> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 //不同  
  10.                 Product product = new Product(Integer.parseInt(tokens[0]), tokens[1], new BigDecimal(tokens[2]));  
  11.                 returnSet.add(product);  
  12.                 line = reader.readLine();  
  13.             }  
  14.         }  
  15.         return returnSet;  
  16.     }  
  17. }  

CustomerCsvReader.java

[java] view plaincopy
  1. public class CustomerCsvReader {  
  2.    
  3.     Set<Customer> getAll(File file) throws IOException {  
  4.         Set<Customer> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 //不同  
  10.                 Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1], tokens[2], tokens[3]);  
  11.                 returnSet.add(customer);  
  12.                 line = reader.readLine();  
  13.             }  
  14.         }  
  15.         return returnSet;  
  16.     }  
  17. }  

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


ProductCsvReader.java after Extract Method

[java] view plaincopy
  1. public class ProductCsvReader {  
  2.    
  3.     Set<Product> getAll(File file) throws IOException {  
  4.         Set<Product> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 Product product = unmarshall(tokens);  
  10.                 returnSet.add(product);  
  11.                 line = reader.readLine();  
  12.             }  
  13.         }  
  14.         return returnSet;  
  15.     }  
  16.   
  17.     Product unmarshall(String[] tokens) {  
  18.         Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],   
  19.                 new BigDecimal(tokens[2]));  
  20.         return product;  
  21.     }  
  22. }  

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


AbstractCsvReader.java

[java] view plaincopy
  1. abstract class AbstractCsvReader {  
  2.   
  3.     Set<Product> getAll(File file) throws IOException {  
  4.         Set<Product> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 Product product = unmarshall(tokens);  
  10.                 returnSet.add(product);  
  11.                 line = reader.readLine();  
  12.             }  
  13.         }  
  14.         return returnSet;  
  15.     }  
  16. }  

ProductCsvReader.java after Pull Up Method

[java] view plaincopy
  1. public class ProductCsvReader extends AbstractCsvReader {  
  2.   
  3.     Product unmarshall(String[] tokens) {  
  4.        Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],   
  5.                 new BigDecimal(tokens[2]));  
  6.         return product;  
  7.     }  
  8. }  

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


AbstractCsvReader.java with abstract unmarshall method

[java] view plaincopy
  1. abstract class AbstractCsvReader {  
  2.   
  3.     Set<Product> getAll(File file) throws IOException {  
  4.         Set<Product> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 Product product = unmarshall(tokens);  
  10.                 returnSet.add(product);  
  11.                 line = reader.readLine();  
  12.             }  
  13.         }  
  14.         return returnSet;  
  15.     }  
  16.   
  17.     abstract Product unmarshall(String[] tokens);  
  18. }  

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


AbstractCsvReader.java with Generics

[java] view plaincopy
  1. abstract class AbstractCsvReader<T> {  
  2.   
  3.     Set<T> getAll(File file) throws IOException {  
  4.         Set<T> returnSet = new HashSet<>();  
  5.         try (BufferedReader reader = new BufferedReader(new FileReader(file))){  
  6.             String line = reader.readLine();  
  7.             while (line != null && !line.trim().equals("")) {  
  8.                 String[] tokens = line.split("\\s*,\\s*");  
  9.                 T element = unmarshall(tokens);  
  10.                 returnSet.add(product);  
  11.                 line = reader.readLine();  
  12.             }  
  13.         }  
  14.         return returnSet;  
  15.     }  
  16.   
  17.     abstract T unmarshall(String[] tokens);  
  18. }  

ProductCsvReader.java with Generics

[java] view plaincopy
  1. public class ProductCsvReader extends AbstractCsvReader<Product> {  
  2.   
  3.     @Override  
  4.     Product unmarshall(String[] tokens) {  
  5.        Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],   
  6.                 new BigDecimal(tokens[2]));  
  7.         return product;  
  8.     }  
  9. }  

CustomerCsvReader.java with Generics

[java] view plaincopy
  1. public class CustomerCsvReader extends AbstractCsvReader<Customer> {  
  2.   
  3.     @Override  
  4.     Customer unmarshall(String[] tokens) {  
  5.         Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1],   
  6.                 tokens[2], tokens[3]);  
  7.         return customer;  
  8.     }  
  9. }  

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


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


0 0
原创粉丝点击