見出し画像

リファクタリング的な作業(5)

(Python学習初心者の試行錯誤・備忘録です)
 前回、データベースを操作する「モデル」のコード
mvctest_model.pyが一段落したので、

で書いていたtest.pyを書き換えます。mvctest_model.pyをimport して、Cardsクラスを利用する形に置き換えます。
 Cardsクラスに用意した関数は、これまでのところ
__init__()
_create_table()
loadfromcsv(csvfilename:str) 
loadfromlist(datalist:list)
clear_table()
select_count_where(condition, *params)
select_topcard_where(condition, *params)
update_topcard(self, condition, *params)
この8つを組み合わせて処理を書きます

import TkEasyGUI as eg
from myspeech_lib import myspeech
from mvctest_model import Cards

def make_main():
    lfont = ('標楷體', 100) #大きい表示用フォント
    sfont = ('Arial', 50) #小さい表示用フォント
    btfont= ('Arial', 20) #ボタン表示用フォント

    #表示サンプル
    myword = '仮置き'; pinyin = 'karioki'
    menu_def = [
        ['ファイル',['読み込み',['CSV::LoadCSV'],  '---', 'Exit']],
    ]

    #メイン画面のレイアウト
    layout_main =[
        [eg.Menu(menu_def)],
        [eg.Combo(values=["zh-TW","ja","en-US"],default_value="zh-TW",key="-LANG-")],
        [eg.Button("発声", font = btfont, key ="btn_speak"),
        eg.Button("連想", font = btfont, key ="btn_show_pinyin"),
        eg.Button("覚えた", font = btfont, key ="btn_OK"),
        eg.Button("まだ", font = btfont, key ="btn_NG")],
        [eg.Text('サマリー仮置き',
        key="summary",font=('Arial', 15))],
        [eg.Text(myword, font = lfont, key="disp_main")],
        [eg.Text(pinyin, font = sfont, key="disp_sub")],
    ]
    return eg.Window("単語暗記 for Windows(Python版)", layout=layout_main, finalize=True)

#コンテンツ更新
def refresh_content(mycards:Cards):
    if mycards.topcard is not None:     
        window["disp_main"].update(mycards.topcard['face'])
        window["disp_sub"].update("")
        window["summary"].update("レベル0:{}個 レベル1:{}個 レベル2:{}個 レベル3:{}個 合計 {}個"\
                                .format(
                                    mycards.select_count_where("level = 0"),
                                    mycards.select_count_where("level = 1"),
                                    mycards.select_count_where("level = 2"),
                                    mycards.select_count_where("level = 3"),
                                    mycards.select_count_where("level >= 0")))

#表示作成
window = make_main()

#最初の読み込み・表示
mycards = Cards()
mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
refresh_content(mycards)

while True:
    event, value = window.read()
    
    if event == eg.WIN_CLOSED or event == "Exit": #終わるとき
        break

    elif event == "LoadCSV": #学習教材をCSVで読み込む処理、古いのは削除
        mycsv = eg.popup_get_file("CSVファイルを選択",
                                file_types=(("All Files", "*.*"), ("CSV Files", "*.csv"), ))  
        mycards.clear_table()
        mycards.loadfromcsv(mycsv)
        mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")

    elif event =="btn_speak":   #読み上げ         
        myspeech(value["-LANG-"],mycards.topcard["face"])

    elif event =="btn_show_pinyin":  #ピンイン表示
        window["disp_sub"].update(mycards.topcard["back"])

    elif event =="btn_OK": #記憶定着OKなら。Levelを一つ上げ、タイムスタンプ更新,表示データ更新
        if mycards.topcard['level'] <= 3:
            mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(mycards.topcard['level']+1,)) #レベルインクリメント
            mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
            refresh_content(mycards)
    
    elif event =="btn_NG": #記憶定着NGなら。Levelを0に戻し、タイムスタンプ更新,表示データ更新
        mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(0,)) #レベルzero
        mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")
        refresh_content(mycards)

window.close()

120行程度から⇒80行程度?少しだけスッキリしました。
動作も問題ないことを確認しました。

ControllerとViewを分けたい

(とくにこの辺、概念や用語の使い方など、いろいろ間違っているかもしれません。初心者が、こう考えた、という備忘録ですので・・。)
 この段階で気になるのは、while True:のループの中でイベント処理するところでmycards というCardsのインスタンスが直接出てきごちゃごちゃ書いて いることですが、これはよろしくないですね。
 画面の設計もmakemain()関数とか、メインのコードにあると、ごちゃごちゃしてしまう。
 ということで切り分けにチャレンジしてみます。

View

 メインで、GUIを操作している部分をView_EGというクラスにまとめてみます。make_main() という関数をほぼそのままクラスにしただけですがこれを mvctest_view.py という別のファイルに切り分けました。Viewの中にウィンドウ変数を抱え込むようにします。この機に、各Widgetの key名もつけなおしました。

mvctest_view.py

import TkEasyGUI as eg

class View_EG:
    def __init__(self) -> None:
        lfont = ('標楷體', 100) #大きい表示用フォント
        sfont = ('Arial', 50) #小さい表示用フォント
        btfont= ('Arial', 20) #ボタン表示用フォント
        
        #表示サンプル
        myword = '仮置き'; pinyin = 'karioki'
        menu_def = [
            ['ファイル',['読み込み',['CSV::LoadCSV'],  '---', 'Exit']],
        ]    

        layout = [
            [eg.Menu(menu_def)],
            [eg.Combo(values=["zh-TW","ja","en-US"],default_value="zh-TW",key="-LANG-")],
            [eg.Button("発声", font = btfont, key ="-BTN_SPEAK-"),
            eg.Button("連想", font = btfont, key ="-BTN_SHOW_BACK-"),
            eg.Button("覚えた", font = btfont, key ="-BTN_MEMORIZE-"),
            eg.Button("まだ", font = btfont, key ="-BTN_FORGET-")],
            [eg.Text('サマリー仮置き',
            key="-DISP_SUMMARY-",font=('Arial', 15))],
            [eg.Text(myword, font = lfont, key="-DISP_MAIN-")],
            [eg.Text(pinyin, font = sfont, key="-DISP_SUB-")],
        ]

        self.window = eg.Window("単語暗記 for Windows(Python版)", layout=layout, finalize=True)

Controller

「コントローラー」のクラスを作って、「処理」のロジックをその中に移し、これも別ファイルに切り分ける。
mvctest_controller.py

from mvctest_model import Cards

class Controller:
    def __init__(self) -> None:
        self.mycards = Cards()

    def loadcsv(self, csvfilename:str) -> None:
        self.mycards.clear_table()
        self.mycards.loadfromcsv(csvfilename)

    def pickupcard(self, lowerlevel=0) -> None:
        self.mycards.select_topcard_where("level >= ? \
                                          ORDER BY level ASC, timestamp ASC ",(lowerlevel,))
    
    def inc_level(self) -> None:
        if self.mycards.topcard['level'] < 3:
            self.mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP "\
                                        ,(self.mycards.topcard['level']+1,)) #レベルインクリメント
            self.mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")

    def zero_level(self)-> None:
        self.mycards.update_topcard("level = ?, timestamp = CURRENT_TIMESTAMP ",(0,)) #レベルzero
        self.mycards.select_topcard_where("level >= 0 ORDER BY level ASC, timestamp ASC ")

    def refresh_view(self,window):
        if self.mycards.topcard is not None:
            window["-DISP_MAIN-"].update(self.mycards.topcard['face'])
            window["-DISP_SUB-"].update("")
            window["-DISP_SUMMARY-"].update("レベル0:{}個 レベル1:{}個 レベル2:{}個 レベル3:{}個 合計 {}個"\
                                    .format(
                                        self.mycards.select_count_where("level = 0"),
                                        self.mycards.select_count_where("level = 1"),
                                        self.mycards.select_count_where("level = 2"),
                                        self.mycards.select_count_where("level = 3"),
                                        self.mycards.select_count_where("level >= 0")))

Main

そして、メインのコードは

from mvctest_view import View_EG,eg
from mvctest_controller import Controller
from myspeech_lib import myspeech    

#表示作成
myview = View_EG()

#最初の読み込み・表示
mycontroller = Controller()
mycontroller.pickupcard()
mycontroller.refresh_view(myview.window)

while True:
    event, value = myview.window.read()
    
    if event == eg.WIN_CLOSED or event == "Exit": #終わるとき
        break

    elif event == "LoadCSV": #学習教材をCSVで読み込む処理、古いのは削除
        mycsv = eg.popup_get_file("CSVファイルを選択",
                                file_types=(("All Files", "*.*"), ("CSV Files", "*.csv"), )) 
        mycontroller.loadcsv(mycsv)
        mycontroller.pickupcard(0)
        mycontroller.refresh_view(myview.window)

    elif event =="-BTN_SPEAK-":   #読み上げ         
        myspeech(value["-LANG-"],mycontroller.mycards.topcard["face"])

    elif event =="-BTN_SHOW_BACK-":  #裏面表示
        myview.window["-DISP_SUB-"].update(mycontroller.mycards.topcard["back"])

    elif event =="-BTN_MEMORIZE-": #記憶定着OKなら。Levelを一つ上げ、タイムスタンプ更新,表示データ更新
        mycontroller.inc_level()
        mycontroller.pickupcard(0)
        mycontroller.refresh_view(myview.window)

    elif event =="-BTN_FORGET-": #記憶定着NGなら。Levelを0に戻し、タイムスタンプ更新,表示データ更新
        mycontroller.zero_level()
        mycontroller.pickupcard(0)
        mycontroller.refresh_view(myview.window)
myview.window.close()

40行程度まで短くなりました。
 当初、while True のループもコントローラーの方に移そうとしたのですが、どうも、うまく書けなかったので、このループ(イベントと、コントローラーの処理とを関連付ける部分)だけメインのコードの中に残すことにしました。
 以上、自己流のリファクタリング作業でした。元のコードに比べれば、分割することである程度、見通しが良くなったのではないかなと思います。


この記事が気に入ったらサポートをしてみませんか?