前編では、MVCアーキテクチャの解説と、簡単なサンプルを紹介しました。
後編では、本格的なMVCアーキテクチャのコードサンプルを見ていきます。
サンプル2:MVCの導入
では、実際にMVCアーキテクチャを大々的に導入したサンプルについて見ていきます。
全体構造
これはゲームアプリのシステムです。
tkinterの作法に従うcontoller(102.py)と画面を描画するView(CanvasData.py)、データを示すModel(MapData.py)から成っています。
実は、このシステムは既に出来上がっていたシステムを、筆者がリファクタリング(コードの改良)を行ったものです。
一応バグがなく動いていましたので、最小限のリファクタリングに留めました。
したがって、本来であればもっとViewやModelからContollerに権限が移譲できるのですが、敢えてしなかったという経緯があります。
それでも、MVCを勉強する上では分かりやすいお手本だと判断しているので、掲載します。
Contoroller
102.py
#通常tkinterはtkと略して使う import tkinter as tk #使うクラスのimport from MapData import MapData from CanvasData import CanvasData #コントローラークラス class Application(tk.Frame): #コンストラクタ。クラスを作ったときに呼ぶ。masterはtk.Tk() def __init__(self,master): super().__init__(master) …システムの制御変数の初期化… #MapDataの初期化。背景と、アイテムの配列を別々に渡す。 self.mapdata = MapData([ [0,0,0,0,0,0,0,0,0,0], [0,2,2,2,0,0,2,2,2,0], [0,2,0,0,0,0,0,0,2,0], [0,2,0,0,0,0,0,0,2,0], [1,2,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,2,0,0,0,0,0,0,2,0], [0,2,0,0,0,0,0,0,2,0], [0,2,2,2,2,0,2,2,2,0], [0,0,0,0,0,0,0,0,0,0] ],[ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0] ]) #CanvasDataの初期化。masterを渡す。 self.canvasdata = CanvasData(master) #CanvasDataのアイテムの初期化 self.canvasdata.init_item(self.orange_hake_count, self.kuro_hake_count, self.orange_roller_count, self.kuro_roller_count, self.small_hummer_count, self.big_hammer_count, self.orange_colorball_count, self.kuro_colorball_count) …ウインドゥタイトルなど… #初期描画 self.canvasdata.paint(self.mapdata, self.cx, self.cy) self.canvasdata.pack() …入力キーに応じた処理… ※一例 def orange_hake_sub(self, y, x): #背景をオレンジに self.mapdata.back_to_orange(y, x) …以下略… ※Canvasと関係した関数の例 #タイル及び自キャラ再描画関数 def repaint(self): self.canvasdata.delete("sai") #CanvasDataのペイントは描画関数 self.canvasdata.paint(self.mapdata, self.cx, self.cy) #メイン。クラスを作って呼んでいるだけ。 win = tk.Tk() app = Application(master=win) app.mainloop()
大枠は前編で紹介したシステムと一緒なので、説明は割愛します。
MapData、CanvasDataのインスタンスを作り、自身のメンバとして保持します。
MapDataは地図のデータです。CanvasDataはtkinterのCanvasに関係した処理をパックしたクラスで、だから初期化の際にFrameを渡しています。
Model
MapData.py
class MapData: #コンストラクタ。背景を10の配列×10、アイテムを10の配列×10でもらう。 def __init__(self, back, item): #backは背景、itemはアイテム self.back = back self.item = item …backとitemを閲覧したり操作したりする関数群… ※一例 #背景が黒ならTrue、オレンジか煉瓦ならFalse def is_back_kuro(self, y, x): if self.back[y][x] == 0: return True else: return False
これは比較的分かりやすいと思います。
コンストラクタでは単純に二つの二次元配列をもらうだけです。
例として挙げたのは、その座標の背景が黒かどうかを返す関数ですね。
このように、データがtkinterから分離されパックされているので分かりやすいです。
View
CanvasData.py
import tkinter as tk #MapDataを使うのでインポート from MapData import MapData class CanvasData: def __init__(self, master): #Tk.Canvasはこのクラスで所持する。そのためにtk.Tk()をもらう。 self.canvas = tk.Canvas(master, width=700, height=500, bg="skyblue") …画像ファイルのロード… …以下、tkinter.Canvasを操作する関数…
このクラスは、Tkinter.Canvas周りの処理をパックしたものです。
これにより、Contollerから描画が消えて、ずいぶんContorollerがすっきりしました。
CanvasDataではMapDataを参照しています。
これは、Modelを直接見て画面を描画したほうが効率が良いとの判断に基づく例外的なものです。
このように、ModelとViewが直接対話するときもあります。
以上が、MVCアーキテクチャの例です。
まとめ:なぜMVCアーキテクチャを導入する必要があるのか?
この2つのサンプルを見て分かることは、
「MVCアーキテクチャを導入するとシステムの構造がすっきりする」
ということです。
使うクラス、使うメンバが局所化され、どこで何をしているのか見通しが良くなります。
これがMVCアーキテクチャの利点です。
MVCアーキテクチャは、この2つのサンプルのように比較的小規模なシステムから大規模なシステムまで適用できます。
システム構築の基本となる考え方だと言えます。
Djangoだと、元よりMVCアーキテクチャなので何も考えなくてもMVCになるのですが、tkinterの場合、自分で構造を組み立てなくてはなりません。
ただ、MVCにする利点はかなりあります。
あなたもMVCアーキテクチャでtkinterを利用してみてください。