PyQt5 MVC로 구현하기 + ClassDiagram

김유상·2022년 11월 21일
0

ICT인턴십

목록 보기
10/21
post-thumbnail

PyQt MVC 패턴 적용하기

이전에 PyQt에 MVC 패턴을 적용하기 어렵다는 얘기를 한 적이 있는데 여러 포스팅을 찾아다닌 끝에 pyqtSignal 기능을 통해 구현할 수 있었다.

pyqtSignal은 QtCore 내부의 클래스인데 특성 상황에 시그널을 발생시킬 수 있고 시그널을 슬롯과 연결해 시그널이 발생할 때마다 슬롯이 실행되도록 만들 수 있다. 일단 직접 적용한 예시를 확인하자.

#뷰
class ImageCheckerView(QMainWindow):
    key_event_signal = QtCore.pyqtSignal(int)

    def keyPressEvent(self, e):
        self.key_event_signal.emit(e.key())

#컨트롤러
class ImageCheckerController():
    def __init__(self) -> None:
        self._app = QApplication(sys.argv)
        self._view = ImageCheckerView()
        self.index = 0
        self.folder = Folder()

        self._view.menu_open_folder.triggered.connect(lambda: self.openFolder())
        self._view.key_event_signal.connect(self.key_press_event)

    def key_press_event(self, key):
        if key == QtCore.Qt.Key_Escape:
            self.close()
        elif key == QtCore.Qt.Key_Left:
            self.prevAction(self.folder.file_list, self.index)
        elif key == QtCore.Qt.Key_Right:
            self.nextAction(self.folder.file_list, self.index)
        elif key == QtCore.Qt.Key_D:
            self.deleteAction(self.folder.file_list, self.index)
            
 #모델
 class Folder():
    def __init__(self, folder_path = "", file_list = []) -> None:
        self.folder_path = folder_path
        self.file_list = file_list
        
 class File():
    def __init__(self, folder_path, file_name) -> None:
        self.folder_path = folder_path
        self.file_name = file_name

위 코드는 MVC 모델이 적용된 것을 확인할 수 있도록 내부적인 뷰 구성 및 비즈니스 로직을 제거한 코드이다. 이렇게 보았을 때, 기존에 해결하지 못한 결합도 문제를 컨트롤러가 온전히 뷰를 소유함으로써 해결하였다. 이제 UI의 변경은 컨트롤러에 전혀 영향을 주지 않는다.(기존의 코드를 수정하는 것은 제외) 컨트롤러 또한 뷰와 모델을 각각 소유함으로써 뷰, 모델 사이의 데이터를 수정으로 인한 오류의 위험 없이 구현할 수 있게 되었다.

뷰와 컨트롤러를 분리하는 핵심 요소는 뷰에 선언된 key_event_signal이다. 이 변수는 pyqtSignal() 객체인데 클래스 내에 정적으로 선언되어야만 제대로 동작할 수 있다. key_event_signal은 뷰에서 keyPressEvent가 발생했을 때, 바로 이벤트를 컨트롤러로 전달하는 역할을 맡는데 내부적으로 구현된 emit함수를 통해 이벤트를 전달한다. 그리고 컨트롤러의 생성자에서 connect되어 있던 key_press_event 함수에 emit의 매개변수를 전달할 수 있다.


PyQt MVC Class Diagram

현재 MVC 패턴을 적용해 만든 Image_Checker_MVC.py의 클래스 다이어그램을 작성해보았다. 아래 사진 왼쪽에 보이는 클래스가 View를 담당하고 있는 클래스이다. 해당 클래스에는 UI와 관련된 오브젝트들이 로컬 변수로 선언되어있고 key_event_signal만이 정적인 클래스 변수로 선언되어 있다. 해당 변수는 keyPressEvent에서 발생하는 키 이벤트를 컨트롤러에 시그널로 전달해주는 역할을 한다.

다음으로 중간에 있는 클래스는 Controller를 담당하는 클래스이다. 해당 클래스는 Client에게 보여주는 애플리케이션을 직접 선언하고 private으로 가지고 있는 _view를 해당 애플리케이션 위에 보여질 수 있도록 한다. 그리고 현재 만들어진 정적인 뷰에 데이터가 보여질 수 있도록 로직을 직접 구현할 수 있다. 이렇게 뷰와 컨트롤러가 분리되면 비즈니스 로직을 작성하는데 제약이 훨씬 줄어들며 유지 보수를 하는데 필요한 비용 또한 줄일 수 있다.

마지막으로 오른쪽에 있는 두 개의 클래스가 모델에 해당하는 클래스이다. File은 Folder에 리스트 형태로 종속되어 있으며 Aggregation 관계로 Folder와 생명 주기를 같이 한다. 하지만 Folder 또한 결국 Controller에 종속된다. 모델의 역할은 Controller가 데이터를 보내고 받기 쉽도록 적절한 추상화를 하는데 있다. Folder 객체는 DB가 없는 환경이면 지금처럼 컨트롤러에 선언될 수 있고 DB 또는 DB역할을 하는 클래스가 있을 경우에는 선언 없이 데이터를 전달하기 위한 모델로써 역할을 하게 된다.

profile
continuous programming

0개의 댓글