Geçenlerde yine bir Qt projesine daldım. Hızlı prototipleme için Qt Designer'da arayüzü çizip, .ui dosyasından pyuic5 ile o sihirli kodu generate etmek... İlk başta mükemmel görünüyordu. Kod temiz, arayüz ayrı, hayat güzel.
Sihirli Başlangıç
Şu klasik yöntem:
setupUi(self)[/CODE] çağrısıyla, tasarladığın tüm widget'lar (QPushButton, QLineEdit) sanki sihirle sınıfının özellikleri haline geliyor. self.pushButton deyip direkt erişebiliyorsun. İlk yaptığımda "Vay be, Qt ne kadar akıllı!" dedim. Gerçekten pratikti.
Dependency Injection Duvarı
Sonra proje büyüdü. Test yazmak, bağımlılıkları yönetmek (Dependency Injection) ve katmanları ayırmak gerekti. İşte o zaman işler çığırından çıktı.
Diyelim ki butona tıklayınca bir servisi (ApiService) çağıracak bir MainPresenter veya ViewModel yazmak istiyorsun. O sihirli kalıtım yüzünden, arayüz (Ui_MainWindow) ve mantık (AnaPencere) sıkı sıkıya birbirine bağlanmış oluyor. AnaPencere sınıfım, Ui_MainWindow'un generate edilmiş tüm widget'larını bilmek ZORUNDA. Test yazmak için bu widget'ları mock'lamak ya da farklı bir arayüz implementasyonu kullanmak neredeyse imkansız hale geliyor.
Kafayı yiyecektim. Şaka gibi ama, o pratik sihir, kodumu test edilemez, esnek olmayan bir yapıya dönüştürmüştü.
Çözüm Arayışı ve "Composition"
StackOverflow'da bile tam istediğim cevabı bulamadım. Meğerse sorun, inheritance (kalıtım) yerine composition (birleştirme) kullanmamakmış.
Sonunda bulduğum pattern şu:
1. .ui dosyasını ve ondan generate edilen kodu (ui_mainwindow.py) sadece bir "görünüm" (View) sınıfına enjekte ediyorsun.
2. Ana penceren, bu görünüm sınıfını bir özellik (self.view) olarak tutuyor.
3. Tüm iş mantığını (business logic) ayrı bir sınıfta (Presenter) yazıyorsun ve bu presenter'a görünümü enjekte ediyorsun.
Kaba bir örnek:
Bu sayede, MainPresenter'ı test ederken, MainView yerine sahte (mock) bir görünüm objesi geçebiliyorsun. Arayüzü değiştirmek istediğinde sadece MainView sınıfını değiştirmen yetiyor.
Sonuç ve İsyan
Evet, ilk baştaki sihirli yöntem hızlı prototip ve küçük araçlar için harika. Ama proje birazcık büyüyünce, o sihir seni esir alıyor. Composition over Inheritance ilkesi burada da kurtarıcı oldu.
Siz de Qt'de böyle bir saçmalık yaşadınız mı? `.ui` dosyalarıyla çalışırken daha temiz, test edilebilir bir yönteminiz var mı? Yoksa ben mi işi abartıyorum? Yorumlara yazın, tartışalım!
Şu klasik yöntem:
Python:
class AnaPencere(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
setupUi(self)[/CODE] çağrısıyla, tasarladığın tüm widget'lar (QPushButton, QLineEdit) sanki sihirle sınıfının özellikleri haline geliyor. self.pushButton deyip direkt erişebiliyorsun. İlk yaptığımda "Vay be, Qt ne kadar akıllı!" dedim. Gerçekten pratikti.
Sonra proje büyüdü. Test yazmak, bağımlılıkları yönetmek (Dependency Injection) ve katmanları ayırmak gerekti. İşte o zaman işler çığırından çıktı.
Diyelim ki butona tıklayınca bir servisi (ApiService) çağıracak bir MainPresenter veya ViewModel yazmak istiyorsun. O sihirli kalıtım yüzünden, arayüz (Ui_MainWindow) ve mantık (AnaPencere) sıkı sıkıya birbirine bağlanmış oluyor. AnaPencere sınıfım, Ui_MainWindow'un generate edilmiş tüm widget'larını bilmek ZORUNDA. Test yazmak için bu widget'ları mock'lamak ya da farklı bir arayüz implementasyonu kullanmak neredeyse imkansız hale geliyor.
Kafayı yiyecektim. Şaka gibi ama, o pratik sihir, kodumu test edilemez, esnek olmayan bir yapıya dönüştürmüştü.
StackOverflow'da bile tam istediğim cevabı bulamadım. Meğerse sorun, inheritance (kalıtım) yerine composition (birleştirme) kullanmamakmış.
Sonunda bulduğum pattern şu:
1. .ui dosyasını ve ondan generate edilen kodu (ui_mainwindow.py) sadece bir "görünüm" (View) sınıfına enjekte ediyorsun.
2. Ana penceren, bu görünüm sınıfını bir özellik (self.view) olarak tutuyor.
3. Tüm iş mantığını (business logic) ayrı bir sınıfta (Presenter) yazıyorsun ve bu presenter'a görünümü enjekte ediyorsun.
Kaba bir örnek:
Python:
class MainView(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow() # Generate edilen sınıfı oluştur
self.ui.setupUi(self) # Arayüzü bu pencereye kur
class MainPresenter:
def __init__(self, view: MainView):
self.view = view
self._connect_signals()
def _connect_signals(self):
self.view.ui.pushButton.clicked.connect(self._on_button_clicked)
def _on_button_clicked(self):
# İş mantığı burada. View'a sadece self.view.ui üzerinden eriş.
print("Butona tıklandı!")
Bu sayede, MainPresenter'ı test ederken, MainView yerine sahte (mock) bir görünüm objesi geçebiliyorsun. Arayüzü değiştirmek istediğinde sadece MainView sınıfını değiştirmen yetiyor.
Evet, ilk baştaki sihirli yöntem hızlı prototip ve küçük araçlar için harika. Ama proje birazcık büyüyünce, o sihir seni esir alıyor. Composition over Inheritance ilkesi burada da kurtarıcı oldu.
Siz de Qt'de böyle bir saçmalık yaşadınız mı? `.ui` dosyalarıyla çalışırken daha temiz, test edilebilir bir yönteminiz var mı? Yoksa ben mi işi abartıyorum? Yorumlara yazın, tartışalım!