He leído mucho sobre este tema en el pasado y he visto algunas charlas interesantes como esta de tío Bob's . Sin embargo, siempre me resulta bastante difícil diseñar correctamente mis aplicaciones de escritorio y distinguir cuáles deben ser las responsabilidades en el lado UI y cuáles en el lado lógico .
Un breve resumen de buenas prácticas es algo como esto. Debe diseñar su lógica desacoplada de la interfaz de usuario, de modo que pueda utilizar (teóricamente) su biblioteca sin importar qué tipo de backend / UI framework. Lo que esto significa es que, básicamente, la interfaz de usuario debe ser lo más simulada posible y el procesamiento pesado debe realizarse en el lado lógico. De lo contrario, literalmente podría usar mi bonita biblioteca con una aplicación de consola, una aplicación web o una de escritorio.
Además, el tío Bob sugiere que las diferentes discusiones sobre qué tecnología usar le dará muchos beneficios (buenas interfaces), este concepto de aplazamiento le permite tener entidades bien probadas altamente desacopladas, eso suena bien, pero aún así es complicado.
Por lo tanto, sé que esta pregunta es una pregunta bastante amplia que se ha discutido muchas veces en todo el Internet y también en toneladas de buenos libros. Así que para obtener algo bueno, publicaré un pequeño ejemplo ficticio tratando de usar MCV en 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_())
El fragmento de código anterior contiene muchas fallas, mientras más obvio es el modelo que se está acoplando al marco de la interfaz de usuario (QObject, señales pyqt). Sé que el ejemplo es realmente ficticio y podría codificarlo en pocas líneas usando una única QMainWindow, pero mi propósito es entender cómo diseñar adecuadamente una aplicación pyqt más grande.
PREGUNTA
¿Cómo podría diseñar correctamente una gran aplicación PyQt usando MVC siguiendo buenas prácticas generales?
REFERENCES
He hecho una pregunta similar a esta aquí