Tôi đã đọc khá nhiều về chủ đề này trong quá khứ và xem một số cuộc nói chuyện thú vị như thế này từ Bác Bob . Tuy nhiên, tôi luôn cảm thấy khá khó khăn để kiến trúc sư các ứng dụng máy tính để bàn của mình một cách chính xác và phân biệt đâu là trách nhiệm ở phía UI và cái nào thuộc về phía logic .
Tóm tắt rất ngắn gọn về thực hành tốt là một cái gì đó như thế này. Bạn nên thiết kế logic tách rời khỏi UI, theo cách đó bạn có thể sử dụng (về mặt lý thuyết) thư viện của mình bất kể loại khung phụ trợ / giao diện người dùng nào. Điều này có nghĩa là về cơ bản, UI nên càng giả càng tốt và việc xử lý nặng nên được thực hiện ở phía logic. Nói cách khác, tôi có thể sử dụng thư viện đẹp của mình với ứng dụng bảng điều khiển, ứng dụng web hoặc máy tính để bàn.
Ngoài ra, chú Bob gợi ý các cuộc thảo luận khác nhau về việc sử dụng công nghệ nào sẽ mang lại cho bạn rất nhiều lợi ích (giao diện tốt), khái niệm trì hoãn này cho phép bạn có các thực thể được thử nghiệm kỹ lưỡng, nghe có vẻ hay nhưng vẫn khó hiểu.
Vì vậy, tôi biết câu hỏi này là một câu hỏi khá rộng đã được thảo luận nhiều lần trên toàn bộ internet và cả tấn sách hay. Vì vậy, để có được thứ gì đó tốt từ nó, tôi sẽ đăng một ví dụ rất nhỏ đang cố gắng sử dụng MCV trên 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_())
Đoạn mã trên chứa rất nhiều sai sót, rõ ràng hơn là mô hình được ghép nối với khung UI (tín hiệu QObject, pyqt). Tôi biết ví dụ này thực sự giả và bạn có thể viết mã trên một vài dòng bằng cách sử dụng một QMainWindow duy nhất nhưng mục đích của tôi là hiểu cách kiến trúc sư một ứng dụng pyqt lớn hơn.
CÂU HỎI
Làm thế nào bạn có thể kiến trúc sư đúng một ứng dụng PyQt lớn bằng cách sử dụng MVC theo các thực tiễn chung tốt?
TÀI LIỆU THAM KHẢO
Tôi đã đặt một câu hỏi tương tự như thế này ở đây