Java 入門指南

單元 18 - 重構

~~學習進度表~~

重構 (refactoring) 是指不改變軟體 (software) 外部行為下,對程式原始碼 (source code) 進行整理、修改,不外是希望提升原始碼的可讀性及更易於維護

Refactoring

簡單說,只要感覺原始碼有些不對勁就該重構,尤其在一個大的開發團隊中,每個小組負責的部份可能都不同,因而常常會發生識別字 (identifier) 命名不一致或是太多類別有共通特性的情況。所以需要適時的重構原始程式碼,這也是軟體開發過程中一項重要的工作。

我們利用數學公式設計一種演算法 (algorithm) ,利用這種演算法來建立密碼表,然而這個演算法並不是最理想的方式,因為這個公式建立的密碼表只有

5 × 10 = 50

依據 a 可能有 5 種數字, b 可能有 10 種數字,因此這個演算法最多就只能產生 50 種密碼表。

可是 26 個英文字母的排列會有

1 × 2 × 3 × ⋯⋯ × 25 × 26 = 4.0329 × 1026

由上可見有非常非常多種。

有沒有更理想的方式呢?有的,事實上我們可以直接攪亂字串 (string) 中字母的順序,這需要另一個攪亂順序的演算法,這在 Java API 中有,另外編碼 toEncode()toDecode() 兩個方法都按照 Unicode 原始數值來做計算也太不直覺了,事實上有更簡單直覺的處理方式。

以下看到重構後的 Encrypt 類別 (class) ,先是 import 的部分

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;

完整的實作檔案可以參考「範例程式碼」的 Encrypt.java

屬性 (field) 的部分是把密碼表字元陣列 (array) 改成字母表字串與密碼表字串

// 字母表
final String letter = "abcdefghijklmnopqrstuvwxyz";
// 密碼表
String code;

使用陣列的目的是方便用 Unicode 值計算,改用字串是因為字串是更複雜的物件,有更多好用的方法。

這裡是直接將屬性 letter 設定成字母表的字串,同時用關鍵字 (keyword) 宣告為 final ,表示這是不可改變的常數值。

建構子 (constructor) 增加為兩個,無參數 (parameter) 的建構子直接建立隨機密碼表

// 無參數建構子
public Encrypt() {
    this.code = "";
    List<String> letterList = new ArrayList<String>(Arrays.asList(this.letter.split("")));
    Collections.shuffle(letterList);
    for (String s : letterList) {
        this.code += s;
    }
        
    System.out.println(this.code);
}

無參數建構子中,先將 code 屬性設定為空字串

this.code = "";

然後將字母表屬性 letter 逐字元轉換成 ArrayListArrayList 是 Java API 中類似陣列的資料結構

List<String> letterList = new ArrayList<String>(Arrays.asList(this.letter.split("")));

注意這裡是先用 String 型態的 split() 方法將 letter 字母表字串以空字串 "" 分割,結果回傳 String 型態的陣列,這個 String 陣列是由 26 個小寫英文字母的子字串組合而成,再用 Arrays 類別的 asList() 將這個 String 陣列轉換成實作 List 介面的物件,最後用 ArrayList 型態的建構子將 List 型態物件轉變為 ArrayList

由於 ArrayList 實作 List 介面,因此 ArrayList 型態的變數可以用 List 宣告。

下面繼續用 Collections 類別的的 shuffle() 方法攪亂 ArrayList 型態的 letterList ,其中每個子字串(字元)的順序

Collections.shuffle(letterList);

再來用 for 迴圈 (loop) 將每一個子字串附加到 code 屬性的最後,使其成為密碼表字串

for (String s : letterList) {
    this.code += s;
}

最後在命令列印出密碼表字串

System.out.println(this.code);

有參數的建構子則是利用字串參數 str 設定屬性 code

// 有參數的建構子
public Encrypt(String str) {
    this.code = str;
}

之所以要多這個有參數的建構子,主要是在往後實作存檔功能的時候,可以直接儲存密碼表,然後由密碼表字串設定 Encrypt 物件。

下面繼續看到重構後的編碼方法 (method)

// 編碼方法
public String toDecode(String inputString) {
    String result = "";
    List<String> inputList = new ArrayList<String>(Arrays.asList(inputString.split("")));
    for (String s : inputList) {
        if (letter.contains(s)) {
            result += this.code.substring(this.letter.indexOf(s), this.letter.indexOf(s)+1);
        }
        else {
            result += s;
        }
    }
        
    return result;
}

編碼方法中先設定區域變數 (local variable) result 用來儲存結果

String result = "";

接下來把輸入字串轉換成 ArrayList

List<String> inputList = new ArrayList<String>(Arrays.asList(inputString.split("")));

下面用 for 迴圈進行編碼

for (String s : inputList) {
    if (letter.contains(s)) {
        result += this.code.substring(this.letter.indexOf(s), this.letter.indexOf(s)+1);
    }
    else {
        result += s;
    }
}

以上利用變數 s 取得輸入字串中的每一個字元字串,然後用屬性 letter 字串的 contains() 判斷變數 s 是否包含在 letter 中,也就是判斷變數 s 是否為英文小寫字母,如果是英文小寫字母,就從屬性 code 擷取在字母表 letter 相同索引值位置的字元字串,將這個字元字串附加到 result 之後,相對不是英文小寫字母,就直接將該字元字串附加到 result 之後。

最後,回傳結果 result

return result;

解碼方法跟編碼方法很相似,除了把 code 換成 letterletter 換成 code

// 解碼方法
public String toDecode(String inputString) {
    String result = "";
    List<String> inputList = new ArrayList>String>(Arrays.asList(inputString.split(""s)));
    for (String s : inputList) {
        if (letter.contains(s)) {
            result += this.letter.substring(this.code.indexOf(s), this.code.indexOf(s)+1);
        }
        else {
            result += s;
        }
    }
        
    return result;
}

執行部份把註解化的程式碼取消註解,執行結果如下

rkuodvxlmyasicgehtnqjpbzwf
There is no spoon.
Tldtd mn cg neggc.
There is no spoon.

這樣 Encrypt 專案的程式碼是不是更容易理解了呢?接下來我們就要進入 GUI 的部份了,在此之前先來認識一下 Java API 與 JavaFX 吧!

相關教學影片

上一頁 單元 17 - 編碼與解碼
回 Java 入門指南首頁
下一頁 單元 19 - 認識 Java API 與 JavaFX
回 Java 教材首頁
回程式語言教材首頁
中英文術語對照
algorithm演算法
array陣列
class類別
constructor建構子
field屬性
identifier識別字
keyword關鍵字
local variable區域變數
loop迴圈
method方法
parameter參數
refactoring重構
software軟體
source code原始碼
string字串
參考資料
1. The Java™ Tutorials: Declaring Classes
2. The Java™ Tutorials: Declaring Member Variables
3. The Java™ Tutorials: Defining Methods
4. The Java™ Tutorials: Providing Constructors for Your Classes
5. The Java™ Tutorials: Returning a Value from a Method
6. The Java™ Tutorials: Using the this Keyword
7. The Java™ Tutorials: Controlling Access to Members of a Class
8. The Java™ Tutorials: Understanding Class Members
9. The Java™ Tutorials: Initializing Fields
重點整理
1. 重構是指重新整理程式碼,讓程式更易於維護。
2. 常見的重構技術包括整理屬性、方法,挑出類別的共通特性定義父類別等等。
問題與討論
1. 什麼是重構?為什麼要替開發好的程式進行重構?
2. Encrppt 類別進行了哪些重構?為什麼要作這些重構?
練習
1. 承接上一個單元的猜數字遊戲,將新專案寫在 Exercise1801 中,修正第一位數可能為 0 的情況。
1. 承上題,將新專案寫在 Exercise1802 中,增加一個屬性 digit ,然後定義兩個建構子,第一個建構子把 digit 設定為 4 ,第二個建構子利用參數設定 digit ,然後繼續建立其他屬性,然後把程式中猜測數量 4 的程式碼都改成 digit