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 逐字元轉換成 ArrayList , ArrayList 是 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 換成 letter , letter 換成 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 。