菜鳥譯文(二)——使用Java泛型構造模板方法模式

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

html

  1. 實例化並初始化一個Reader來讀取CSV文件;
  2. 讀取每一行並解析;
  3. 把每一行的字符填充到Product或Customer對象;
  4. 將每個對象添加到Set裏;
  5. 返回Set。


正如你看到的,只有有註釋的地方是不同的。其餘全部步驟都是相同的。
java

 

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提取帶註釋的部分:
git

 

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重構這個父類。
github

 

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編譯會報錯。爲了解決這個問題咱們使用泛型。
ide

 

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 測試


翻譯的很差,歡迎拍磚! spa

相關文章
相關標籤/搜索