Python 入門指南 5.0

單元 34 - 整合 Encrypt 類別

~~學習進度表~~

我們把 M ,也就是編碼功能核心的 Encrypt 類別 (class) 放在 encrypt.py 中, V 的 EncryptView 類別放在 encrypt_view.py 中,最後 C 的 EncryptController 類別放在 encrypt_controller.py 裡

EncryptView
↑
M   V   C
↓      ↓
      Encrypt   EncryptController

完整程式請參考「範例程式碼」的 encrypt.py 、 encrypt_view.py 及 encrypt_controller.py 。

為什麼要這麼做呢?新的 EncryptView 類別跟 EncryptController 類別難道不能一樣放在 encrypt.py 裡嗎?或是直接擴充原本的 Encrypt 類別不就可以了嗎?

當然可以, EncryptView 類別跟 EncryptController 類別放在 encrypt.py 裡或是擴充 Encrypt 類別都是沒關係的,可是這樣一來的話,如果我們需要在其他地方運用 Encrypt 類別編密碼的功能,就會發生大大的問題。

例如在 Brython 篇的網頁介面,網頁介面並不能用 Tk ,此外手機平台 iOS 或 Android 也沒有原生支援 Tk ,因此如果直接把 Encrypt 擴展為運行在 Tk 之上的類別,這樣的設計導致程式碼重複利用性大大降低,反倒分開來後,核心歸核心,介面歸介面,每個模組 (module) 的設計都功能專一簡單化。

Tk 支援的平台請參考 Tk 官網 Tcl/Tk Platform Support 。

這裡請將 encrypt_controller01.py 改名 encrypt_controller.py ,然後將 encrypt.py 加入到相同的資料夾中,接下來請依序替換 EncryptController 類別中各個方法的實作。

下面直接看到已完成的程式碼 encrypt_controller.py ,首先要從 encrypt 模組引入 Encrypt 類別

 4
from encrypt import Encrypt

然後在 __init__() 方法中要多三個實體屬性 (attribute) ,屬性 e 預備用來儲存 Encrypt 物件 (object) , userinput 為儲存使用者輸入的文字, result 則是編碼結果

15
17
19
        self.e = None
        self.userinput = ""
        self.result = ""

userinput 與 result 的初值都設定為空字串 (string) ,而 Encrypt 物件預計用 e 儲存,這裡同樣初值先設定為 None 。

這裡,我們預期使用者按下新建按鈕就可以得到新的密碼表,因此相對應的 new_method() 方法 (method) 就是用來新建 Encrypt 物件,實作如下

34
36
38
    def new_method(self):
        self.e = Encrypt()
        self.app.display_text["text"] = self.e

new_method() 建立新的 Encrypt 物件,然後在狀態列顯示密碼表。

由於重構後的 Encrypt 類別有 __str__() 方法,因此直接指派 Encrypt 物件給需要字串的地方,就會指派 __str__() 的回傳值 (return value) 。

來執行看看囉

這樣就整合好 Encrypt 類別了,接下來思考如何實作編碼與解碼有關的方法,雖說是用 Encrypt 類別的 toEncode() 方法進行編碼,但在 GUI 仍須考慮實際的情況。例如使用者 (user) 可能沒有輸入任何文字,或是沒有先按新建建立新的 Encrypt 物件就按下編碼等等,以上為有可能發生無法執行的情況,甚至有可能導致程式 (program) 就此當掉。

實際上若是發生類似的情況, Python 直譯器 (interpreter) 還不致於當掉,倒是會發起例外 (exception) 結束或暫停程式執行,可是這樣程式跑起來就顯得卡卡的,總是給使用者一些不舒服的感覺。這樣先做個例外處理 (exception handling) 不外是個好的程式設計建議。

Python 用 try 偵測例外情況,但其實如果能不做例外處理就不做例外處理的好,為什麼呢?因為例外處理說起來讓直譯器先去檢查是否發生例外,這會讓程式執行上增加許多負擔,簡單說,需要額外的記憶體空間與處理器時間,也許在我們這樣的小程式看不出來,但是在大型程式中,這個缺點就顯而易見了。

不用例外處理的話,這樣的問題要解決也不難。因為 userinput 有設置初值為空字串,若使用者沒有輸入文字的話,從 ifd 取得的值也會是空字串,另外沒有新建 e 的話, e 的初值就是 None 。

因此,我們用 if-else 檢查可能的情況就可以了。編碼在 GUI 中為 encode_method() 方法,程式如下

71
73
75
77
78
80
82
83
85
87
89
91
93
95
    def encode_method(self):
        self.userinput = self.app.input_field.get()
        if self.userinput == "":
            self.app.display_text["text"] = m = "沒有輸入!"
        else:
            if self.e == None:
                self.app.display_text["text"] = "沒有密碼物件!"
            else:
                self.result = self.e.toEncode(self.userinput)
                self.app.output_field.config(state="abled")
                self.app.output_field.delete(0, 200)
                self.app.output_field.insert(0, self.result)
                self.app.output_field.config(state="disabled")
                self.app.display_text["text"] = "編碼成功!"

如果使用者沒有輸入文字,訊息欄就顯示 "沒有輸入!" ,同樣的沒有新建 e ,訊息欄也提示相關訊息 "沒有密碼物件!" ,而使用者有輸入文字亦有新建 Encrypt 物件的話,就把結果顯示到 ofd ,訊息欄就顯示 "編碼成功!" 。

注意在第 87 行,這裡先解鎖輸出欄位

87
                self.app.output_field.config(state="disabled")

這是因為 Tk 的文字輸入方塊在鎖住情況下無法插入文字,因此先解鎖,刪除內容,然後在第 93 行重新鎖住輸出欄位

93
                self.app.output_field.config(state="abled")

解碼方法 decode_method() 幾乎與 encode_method() 完全相同,除了把 toEncode() 換成 toDecode() 之外

 98
100
102
104
105
107
109
110
112
114
116
118
120
122
    def decode_method(self):
        self.userinput = self.app.input_field.get()
        if self.userinput == "":
            self.app.display_text["text"] = "沒有輸入!"
        else:
            if self.e == None:
                self.app.display_text["text"] = "沒有密碼物件!"
            else:
                self.result = self.e.toDecode(self.userinput)
                self.app.output_field.config(state="abled")
                self.app.output_field.delete(0, 200)
                self.app.output_field.insert(0, self.result)
                self.app.output_field.config(state="disabled")
                self.app.display_text["text"] = "解碼成功!"

因為 toEncode() 跟 toDecode() 幾乎完全一樣,那是否可以合併為一個方法呢?答案當然是可以的,但是這樣就需要額外的參數 (parameter) 來判斷是要用編碼還是解碼功能。

來執行看看囉!下面是編碼

下面是解碼

這樣的編碼效果還不錯,倒是我們想保存密碼表的話,就要實作存檔與載入的功能囉!

中英文術語對照
屬性attribute
類別class
例外exception
例外處理exception handling
直譯器interpreter
方法method
模組module
物件object
參數parameter
程式program
回傳值return value
字串string
使用者user
重點整理
1. 每個類別、模組的設計都應該要功能專一簡單化。
2. __init__() 方法中的每個實體屬性都指派初值。
問題與討論
1. 為什麼類別、模組的設計要功能專一簡單化?
2. 為什麼在 __init__() 方法中要給實體屬性指派初值?
練習
1. 寫一個程式 exercise3401.py ,設計一個 DiceGUI 視窗,利用按下按鈕顯示單元 29 練習 8 的 get_result() 結果。 參考程式碼
2. 承上題,將新程式寫在 exercise3402.py 中,將顯示文字改成逐行的文字動畫。 參考程式碼
3. 承上題,將新程式寫在 exercise3403.py 中,將顯示文字改成單元 23 練習 3 的 Dice 點數。 參考程式碼
4. 承上題,將新程式寫在 exercise3404.py 中,將顯示文字改成 1 到 6 之間的隨機動畫,最後停留在 Dice 點數。 參考程式碼
5. 承上題,將新程式寫在 exercise3405.py 中,在動畫期間鎖住按鈕,並且改變動畫數字的背景顏色。 參考程式碼
6. 承上題,將新程式寫在 exercise3406.py 中,改成兩顆骰子的動畫。 參考程式碼
7. 承上題,將新程式寫在 exercise3407.py 中,改成每一次按下按鈕都得到不同的骰子結果。 參考程式碼
8. 承上題,將新程式寫在 exercise3408.py 中,利用單元 25 練習 3 的 DiceUser ,增加文字顯示欄位顯示遊戲結果。 參考程式碼
9. 承上題,將新程式寫在 exercise3409.py 中,改進程式把遊戲結果放到骰子動畫結束才顯示。 參考程式碼
10. 承上題,將新程式寫在 exercise3410.py 中,加入遊戲結束機制。 參考程式碼

上一頁 單元 33 - C 的部分與設定 command
回 Python 入門指南 5.0 首頁
下一頁 單元 35 - 存檔與載入
回 Python 教材首頁
回程式語言教材首頁