PythonでGUIアプリやゲームを作り始めると、最初は「とりあえず動けばいい」と思って、1つのファイルにどんどんコードを書いてしまいがちです。
Pythonでクラスを分ける設計の考え方【GUI・ロジック・AIを分離する】
PythonでGUIアプリやゲームを作り始めると、最初は「とりあえず動けばいい」と思って、1つのファイルにどんどんコードを書いてしまいがちです。
私も最初はそうでした。
現在開発している麻雀CPUも、最初はとにかく動くものを作ることを優先していたため、GUIのクラスにゲーム進行やCPUの思考など、さまざまな処理を書いていました。
しかし、機能が増えるにつれて少しずつコードの管理が難しくなり、現在は役割ごとにクラスを分ける設計へ変更しています。
今回は、「どのようにクラスを分けたか」ではなく、私がどんなタイミングで設計を見直し、なぜクラスを分けるようになったのかという考え方を紹介したいと思います。
私の開発スタイル
前回のシリーズ記事でも書きましたが、私は最初から細かく設計するタイプではありません。
まずは多少エラーがあっても、多少動作がおかしくても、「とにかく動くもの」を作ることを優先しています。
実際に動かしてみると、
「ここは使いにくいな」
「この機能も追加したいな」
という改善点が見えてくるからです。
そのため、
- まずは動くものを作る
- 実際に使ってみる
- 問題点を見つける
- 必要になったら設計を見直す
という流れで開発しています。
今回紹介する構成も、最初から考えていたものではありません。
開発を進める中で何度も作り直し、少しずつ今の形になりました。
私が設計を見直すタイミング
私は「コードが〇〇行になったらクラスを分ける」といったルールは決めていません。
クラスを分け始めるタイミングは、とてもシンプルです。
「修正した影響範囲が自分で把握できなくなったとき」です。
例えば、
GUIを少し修正しただけなのにCPUの処理まで影響を受けたり、画像認識を変更したら画面表示までおかしくなったり。
そんなことが起き始めると、
「そろそろ整理した方がいいな」
と感じます。
また、新しい機能を追加するときに、
「この処理ってどこに書けばいいんだろう?」
と迷うようになったときも、設計を見直すサインだと考えています。
個人開発では、自分しかコードを書きません。
だからこそ、「未来の自分」が理解できるコードになっているかを意識するようにしています。
オブジェクト指向は「部品を作る考え方」
オブジェクト指向という言葉を聞くと、
- カプセル化
- 継承
- ポリモーフィズム
といった難しい用語を思い浮かべる方も多いと思います。
もちろん、それらも大切な考え方です。
ただ、私自身はもっとシンプルに考えています。
「役割を持った部品を作ること」
これがオブジェクト指向の一番分かりやすい考え方だと思っています。
例えば麻雀CPUなら、
- GUIは画面を表示する部品
- GameManagerはゲーム全体を管理する部品
- MahjongDecisionはCPUが考える部品
- CameraWorkerはカメラ映像を取得する部品
- HaiDetectionは牌を認識する部品
というように、それぞれが一つの仕事だけを担当します。
部品ごとに役割を分けることで、お互いの中身を細かく知らなくても動作するようになります。
私はこれが、オブジェクト指向の一番大きなメリットだと感じています。
クラスを分けるメリット
クラスを分けて一番良かったと思うのは、「修正する場所がすぐに分かる」ことです。
例えばCPUの捨て牌ロジックを改善したいなら、MahjongDecisionを見ればいい。
画面レイアウトを変更したいなら、GUIだけを見ればいい。
「どこを修正すればいいか」が明確になります。
また、一つの修正が他の機能へ影響する可能性も小さくなりました。
以前はGUIを修正するとCPUまで動かなくなることがありましたが、役割を分けた現在は、そのようなことはかなり減りました。
さらに、新しい機能も追加しやすくなりました。
麻雀CPUでは、リーチ機能や振り聴判定、他家の捨て牌管理など、開発途中で追加した機能がたくさんあります。
役割が整理されているおかげで、
「この機能なら、このクラスに追加しよう」
と自然に判断できるようになりました。
私にとってクラス設計は、きれいなコードを書くためではありません。
数週間後、あるいは数か月後の自分が「どこを直せばいいか分かる状態」を作るためのものです。
現在の構成
現在は、おおまかに次のような構成で開発しています。
GUI
│
▼
GameManager
├── MahjongDecision
└── HaiDetection
▲
│
CameraWorker
それぞれの役割はシンプルです。
- GUI:画面表示とユーザー操作
- GameManager:ゲーム全体の管理
- MahjongDecision:CPUの思考
- CameraWorker:カメラ映像の取得
- HaiDetection:牌画像の認識
例えばGUIは「ボタンが押された」という情報をGameManagerへ渡すだけです。
CPUが何を切るかを考えるのはMahjongDecisionの役割であり、GUIはその内容を知る必要はありません。
また、CameraWorkerは映像を取得するだけで、牌を認識する処理はHaiDetectionが担当しています。
このように役割を分けることで、それぞれのクラスが「1つの仕事」に集中できるようにしています。
まとめ
私は最初から細かく設計することはありません。
まずは動くものを作り、「管理しづらくなってきたな」と感じたタイミングで設計を見直しています。
私にとってクラス設計は、「正しい設計」を目指すためではなく、未来の自分が困らないように整理するための手段です。
もし最近、
「修正するたびに別の場所まで壊れる」
「どこを直せばいいか分からなくなってきた」
と感じているなら、それはクラスを分けるタイミングなのかもしれません。



コメント