若是你發現你有不少重複的代碼,你可能會考慮用模板方法消除容易出錯的重複代碼。這裏有一個例子:下面的兩個類,完成了幾乎相同的功能:
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