Java 入門指南 - Encrypt 類別

利用 Java 寫程式 (program) ,著重在設計類別 (class) ,然後規劃物件 (object) 的使用、物件與物件間的互動,同時要注意,一個物件專心做好一件事情就好,類別的設計不應該太過複雜




我們打算開始發展一個 Encrypt 類別,它的主要功能是建立一個英文小寫字母的對換表格,藉由這個表格,我們可以對英文句子中的小寫英文字母進行對換,例如 "There is no spoon." 可能變成以下任一個
Tfqdq ki jo itooj.
Tcnan hf gl fqllg.
Tczmz dn ij nkjji.
Tgfsf pb ir barri.
Tdcpc my fo yxoof.


其中, EncryptDemo 為我們在命令列 (command line) 測試 Encrypt 的類別,最重要的是我們還要發展使用者介面 (user interface) 的類別,給使用者輕鬆操作的圖形使用者介面 (graphical user interface, GUI) ,也就是 EncryptorGUI 類別,而 EncryptorGUIDemo 為執行 EncryptorGUI 的類別。


這是說, Encrypt 與 EncryptorGUI 都不會有 main() 方法,我們把建立對換表格的工作交給 Encrypt ,使用者的 GUI 介面的設計交給 EncryptorGUI 。而 EncryptDemo 與 EncryptorGUIDemo 都只有 main() 方法, EncryptDemo 中建立 Encrypt 物件,並在命令列印出測試結果, EncryptorGUIDemo 中同樣建立 Encrypt 物件,但有更多功能,其 GUI 外觀如下圖



有兩個可供輸入的文字欄位 (textfield) ,其中一個作為我們輸出顯示訊息之用,另有三個標籤 (label) ,顯示文字提示訊息,七個按鈕 (button) ,提供「新建」、「開啟」、「儲存」 Encrypt 物件,與「編碼」、「解碼」所輸入的英文句子,「清除」所有輸入欄位,以及「拷貝」輸出結果等的功能。


為什麼要設計這麼多的類別,不把所有功能塞進一個類別就好了呢?好比寫個 main() ,然後把所有程式碼塞進 main() 當中,同樣可以達到我們想要的程式。理由其實很單純,我們大可發展一個功能完備可在命令列大展神威的 Encrypt ,可是換在 GUI 中,很多細節必須重新設計,幾乎等於重寫,因此我們把功能與使用者介面分開來,實際編碼、解碼的工作交給 Encrypt ,因此命令列使用 EncryptDemo 操作,可直接利用 Encrypt , GUI 由 EncryptorGUI 設計,也可重複利用 Encrypt ,這樣一來,我們就不需要為命令列版本與 GUI 版本重複寫進相同的 Encrypt 程式碼。


事實上, API 中許多類別都是基於這樣的理念設計的,一個類別專心做好一件事情就好 :)


現在我們先來看看 Encryptor 所有功能的核心,也就是 Encrypt 類別,我們的目的是,建立一個小寫英文字母的轉換表格,然後編碼、解碼都可直接依據這個表格。首先,我們先來看看建立表格的數學公式
y = a * x + b
m = y % n
r = m + diff


我們的概念很簡單,假設 x 為字元的原始編碼, Unicode 編碼中 'a' 為 97 ,其後依次遞增 1 ,然後我們將 x 乘上變數 (variable) a , 再加上變數 b ,兩者均是 0 到 9 的隨機整數,這樣便得到 y 的值。


然後將 y 除以 n 取得餘數 m , n 為所要轉換的字元數量,英文小寫字母共有 26 個,所以這裡 n 等於 26 ,因此 m 等於 0 到 25 之間的整數值。最後將 m 加上 diff , diff 也就是編碼系統的差值,由於 Unicode 中 'a' 為 97 ,所以這裡 diff 要以 97 代入。


因此,餘數 0 的字元會替換成 'a' ,餘數 1 的字元會被替換成 'b' ,餘數 2 的字元會被替換成 'c' ,餘下 23 個字元類推。這樣的計算需要進行 n 次,也就是 26 次,我們最後得到一組餘數與相對應字元的表格,這就是我們需要的表格了。


重複 n 次,我們需要一個迴圈 (loop) ,由於重複次數確定,因此 for 迴圈 (for loop) 很適合,那我們要用什麼東西來儲存這個表格呢?嗯, API 中有許多的資料結構 (data structure) ,可以依資料特性有效率的處理資料,這裡,我們利用陣列 (array) 就可以了。


陣列是固定大小的資料結構,當宣告陣列後,同時需要宣告陣列的型態與大小,也就是說,陣列的元素 (element) 數量是固定的,同時這些元素必須是相同的資料型態,而且,陣列的的索引值由 0 開始,因此完全符合我們計算餘數從 0 開始的需求。


因此, Encrypt 需要以下的屬性 (field) ,我們先給給初值進行測試, a 給 3 , b 給 5 好了
char[] cArray = new char[26];
int a = 3;
int b = 5;
int n = 26;    // 字元數量
char c = 'a';  // 起始字元
int diff = 97; // 起始字元的編碼值


資料型態後接著中括弧便表示屬於該資料型態的陣列,因此 char[] 表示字元陣列,後面接著變數 cArray 便是對字元陣列的參考 (reference) ,後頭用 new 建立新的陣列物件,其中 char[26] 的 26 表示字元陣列 cArray 中可以儲存的元素個數。


整數 a 、 b 、 n 與 diff 都是公式中需要用到的值, c 為字元型態,表示起始字元。公式中另有計算過程產生的變數,如 x 、 y 、 m 、 r 等,這些屬於寫在建立 cArray 的 setArray() 方法 (method) 中的區域變數 (local variable)
public void setArray() {
    // 方法中的區域變數
    int i, y, x, m;
    char r;
    
    x = c; // x 得到 c 的 Unicode 編碼數字
    for (i = 0; i < n; i++) {
        y = x * a + b;
        m = y % n;
        r = (char) (m + diff); // 這裡用到強制型態轉換
                               // r 為計算後得到的字元
        cArray[i] = r;
        x++; // x 遞增 1 才會到下一個字元
    }        
}


區域變數可以說是計算過程中所需要的暫存變數,也就是我們不會在其他地方存取或使用的變數,這些變數單獨在方法裡宣告、建立即可。


setArray() 有幾個觀念需要解釋,首先
x = c;


屬性 c 是 char 字元型態,而 x 為整數型態,這裡將字元型態的值指派給整數型態的變數,那麼 x 中得到的會是字元的 Unicode 的 Unicode 編碼值,由於 c 已經指派初值為 'a' ,因此 x 會是整數 97 。然後
r = (char) (m + diff);


小括弧圍著 char ,這是強制型態轉換的方式,由於 m 及 diff 都是整數 int 型態,經過小括弧的強制型態轉換,會將 (m + diff) 轉換為字元 char 型態,例如,如果 m 等於 0 , 0 + 97 等於 97 那麼最後 r 會得到字元 'a' 。


我們先寫一個簡單的程式來看看轉換後的表格長怎麼樣吧!
// Encrypt 的測試類別
// 使用 y = 3 * x + 5
public class Encrypt01 {
    char[] cArray = new char[26];
    int a = 3;
    int b = 5;
    int n = 26;    // 字元數量
    char c = 'a';  // 起始字元
    int diff = 97; // 起始字元的編碼值
    
    public void setArray() {
        // 方法中的區域變數
        int i, y, x, m;
        char r;
        
        x = c; // x 得到 c 的 Unicode 編碼數字
        for (i = 0; i < n; i++) {
            y = x * a + b;
            m = y % n;
            r = (char) (m + diff); // 這裡用到強制型態轉換
                                   // r 為計算後得到的字元
            cArray[i] = r; 
            x++; // x 遞增 1 才會到下一個字元
        }        
    }
    
    public static void main(String[] args) { 
        Encrypt01 t = new Encrypt01();
        t.setArray();
        System.out.println();
        System.out.println(t.cArray);
        System.out.println();
    }
}

/* 《程式語言教學誌》的範例程式
    http://pydoing.blogspot.com/
    檔名:Encrypt01.java
    功能:示範 Java 程式 
    作者:張凱慶
    時間:西元 2011 年 4 月 */


編譯後執行,結果如下



結果如預期,一個英文小寫字母恰恰好對到另一個英文小寫字母,這樣我們的公式就 ok 了嗎?嗯, a 與 b 都可以是 0 到 9 之間的任意整數,有 100 種組合說,所以,我們需要繼續測試是不是每一種組合都 ok 囉!


中英文術語對照
程式program
類別class
物件object
命令列command line
使用者介面user interface
圖形使用者介面graphical user interface, GUI
文字欄位textfield
標籤label
按鈕button
變數variable
for 迴圈for loop
資料結構data structure
陣列array
元素element
屬性field
方法method
區域變數local variable


您可以繼續參考
軟體開發


相關目錄
回 Java 入門指南
回 Java 教材目錄
回首頁


參考資料
The JavaTM Tutorials: Getting Started
The JavaTM Tutorials: Learning the Java Language
The JavaTM Tutorials: Essential Classes
The Java Language Specification, Third Edition


本文於 2013 年 1 月訂正
本文於 2013 年 1 月修正

2 則留言:

Unknown 提到...

您好,新手求教,在讀了您此篇教學後,發現了兩點疑惑,請問:

1. 關於您在setArray()跟Encrypt 的測試類別中寫到「m = d % n;」,其中d是否應該要改成y才正確呢?

2. 在Encrypt 的測試類別中的「Encrypt01 t = new Encrypt();」,其中new後面的Encrypt()是否應該改成Encrypt01()才對?

Kaiching Chang 提到...

兩個地方的確都打錯了,已修改,感謝指正 :)