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.pyencrypt_view.pyencrypt_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 = ""

userinputresult 的初值都設定為空字串 (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 練習 8get_result() 結果。 參考程式碼
2. 承上題,將新程式寫在 exercise3402.py 中,將顯示文字改成逐行的文字動畫。 參考程式碼
3. 承上題,將新程式寫在 exercise3403.py 中,將顯示文字改成單元 23 練習 3Dice 點數。 參考程式碼
4. 承上題,將新程式寫在 exercise3404.py 中,將顯示文字改成 16 之間的隨機動畫,最後停留在 Dice 點數。 參考程式碼
5. 承上題,將新程式寫在 exercise3405.py 中,在動畫期間鎖住按鈕,並且改變動畫數字的背景顏色。 參考程式碼
6. 承上題,將新程式寫在 exercise3406.py 中,改成兩顆骰子的動畫。 參考程式碼
7. 承上題,將新程式寫在 exercise3407.py 中,改成每一次按下按鈕都得到不同的骰子結果。 參考程式碼
8. 承上題,將新程式寫在 exercise3408.py 中,利用單元 25 練習 3DiceUser ,增加文字顯示欄位顯示遊戲結果。 參考程式碼
9. 承上題,將新程式寫在 exercise3409.py 中,改進程式把遊戲結果放到骰子動畫結束才顯示。 參考程式碼
10. 承上題,將新程式寫在 exercise3410.py 中,加入遊戲結束機制。 參考程式碼

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