Я много читал об этом в прошлом и смотрел некоторые интересные разговоры, подобные этой, от дяди Боба . Тем не менее, я всегда нахожу довольно сложным правильно спроектировать мои настольные приложения и различать, какие должны быть обязанности на стороне пользовательского интерфейса, а какие - на стороне логики .
Очень краткое изложение хороших практик - что-то вроде этого. Вы должны спроектировать свою логику, отделенную от пользовательского интерфейса, чтобы вы могли (теоретически) использовать вашу библиотеку независимо от того, какой тип инфраструктуры бэкэнда / пользовательского интерфейса. По сути, это означает, что пользовательский интерфейс должен быть настолько пустым, насколько это возможно, а тяжелая обработка должна выполняться со стороны логики. Иначе говоря, я мог бы буквально использовать свою симпатичную библиотеку с консольным приложением, веб-приложением или настольным приложением.
Кроме того, дядя Боб предлагает различные дискуссии о том, какая технология использовать даст вам много преимуществ (хорошие интерфейсы), эта концепция отсрочки позволяет вам иметь хорошо открепленные хорошо протестированные объекты, что звучит замечательно, но все еще сложно.
Итак, я знаю, что этот вопрос - довольно широкий вопрос, который много раз обсуждался во всем Интернете, а также в тоннах хороших книг. Поэтому, чтобы получить что-то хорошее из этого, я опубликую очень небольшой пример с попыткой использовать MCV на pyqt:
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_())
Приведенный выше фрагмент содержит множество недостатков, более очевидным из которых является то, что модель связана с каркасом пользовательского интерфейса (QObject, pyqt сигналы). Я знаю, что пример действительно глупый, и вы могли бы закодировать его в несколько строк, используя одно окно QMainWindow, но моя цель - понять, как правильно спроектировать большое приложение pyqt.
ВОПРОС
Как бы вы правильно спроектировали большое приложение PyQt, используя MVC, следуя хорошей общей практике?
ССЫЛКИ
Я сделал подобный вопрос к этому здесь