私は過去にこの主題について多くのことを読み、ボブおじさんのこのような興味深い講演をいくつか見てきました。それでも、デスクトップアプリケーションを適切に設計し、UI側の責任とロジック側の責任を区別することは常に非常に難しいと感じています。
優れた実践の非常に短い要約は、このようなものです。UIから切り離したロジックを設計する必要があります。これにより、どの種類のバックエンド/ UIフレームワークに関係なく(理論的に)ライブラリを使用できるようになります。これが意味することは、基本的にUIは可能な限りダミーであるべきであり、重い処理はロジック側で行われるべきだということです。別の言い方をすれば、文字通り、コンソールアプリケーション、Webアプリケーション、またはデスクトップアプリケーションで素敵なライブラリを使用できます。
また、ボブおじさんは、どのテクノロジーを使用するとさまざまなメリットが得られるか(良いインターフェース)を議論することを提案します。
ですから、この質問は非常に広範な質問であり、インターネット全体で何度も議論されてきました。そこで、何か良いものを得るために、pyqtでMCVを使用しようとする非常に小さなダミーの例を投稿します。
import sys
import os
import random
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import QtCore
random.seed(1)
class Model(QtCore.QObject):
    item_added = QtCore.pyqtSignal(int)
    item_removed = QtCore.pyqtSignal(int)
    def __init__(self):
        super().__init__()
        self.items = {}
    def add_item(self):
        guid = random.randint(0, 10000)
        new_item = {
            "pos": [random.randint(50, 100), random.randint(50, 100)]
        }
        self.items[guid] = new_item
        self.item_added.emit(guid)
    def remove_item(self):
        list_keys = list(self.items.keys())
        if len(list_keys) == 0:
            self.item_removed.emit(-1)
            return
        guid = random.choice(list_keys)
        self.item_removed.emit(guid)
        del self.items[guid]
class View1():
    def __init__(self, main_window):
        self.main_window = main_window
        view = QtWidgets.QGraphicsView()
        self.scene = QtWidgets.QGraphicsScene(None)
        self.scene.addText("Hello, world!")
        view.setScene(self.scene)
        view.setStyleSheet("background-color: red;")
        main_window.setCentralWidget(view)
class View2():
    add_item = QtCore.pyqtSignal(int)
    remove_item = QtCore.pyqtSignal(int)
    def __init__(self, main_window):
        self.main_window = main_window
        button_add = QtWidgets.QPushButton("Add")
        button_remove = QtWidgets.QPushButton("Remove")
        vbl = QtWidgets.QVBoxLayout()
        vbl.addWidget(button_add)
        vbl.addWidget(button_remove)
        view = QtWidgets.QWidget()
        view.setLayout(vbl)
        view_dock = QtWidgets.QDockWidget('View2', main_window)
        view_dock.setWidget(view)
        main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, view_dock)
        model = main_window.model
        button_add.clicked.connect(model.add_item)
        button_remove.clicked.connect(model.remove_item)
class Controller():
    def __init__(self, main_window):
        self.main_window = main_window
    def on_item_added(self, guid):
        view1 = self.main_window.view1
        model = self.main_window.model
        print("item guid={0} added".format(guid))
        item = model.items[guid]
        x, y = item["pos"]
        graphics_item = QtWidgets.QGraphicsEllipseItem(x, y, 60, 40)
        item["graphics_item"] = graphics_item
        view1.scene.addItem(graphics_item)
    def on_item_removed(self, guid):
        if guid < 0:
            print("global cache of items is empty")
        else:
            view1 = self.main_window.view1
            model = self.main_window.model
            item = model.items[guid]
            x, y = item["pos"]
            graphics_item = item["graphics_item"]
            view1.scene.removeItem(graphics_item)
            print("item guid={0} removed".format(guid))
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        # (M)odel ===> Model/Library containing should be UI agnostic, right now it's not
        self.model = Model()
        # (V)iew      ===> Coupled to UI
        self.view1 = View1(self)
        self.view2 = View2(self)
        # (C)ontroller ==> Coupled to UI
        self.controller = Controller(self)
        self.attach_views_to_model()
    def attach_views_to_model(self):
        self.model.item_added.connect(self.controller.on_item_added)
        self.model.item_removed.connect(self.controller.on_item_removed)
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    form = MainWindow()
    form.setMinimumSize(800, 600)
    form.show()
    sys.exit(app.exec_())
上記のスニペットには多くの欠陥が含まれており、モデルがUIフレームワーク(QObject、pyqtシグナル)に結合されていることがより明白です。この例は本当にダミーであり、単一のQMainWindowを使用して数行でコーディングできますが、私の目的は、より大きなpyqtアプリケーションを適切に設計する方法を理解することです。
質問
適切な一般的なプラクティスに従って、MVCを使用して大きなPyQtアプリケーションをどのように適切に設計しますか?
参考文献