Design Patterns in Qtby Matthias Kalle Dalheimer, author of Programming with Qt, 2nd Edition
The so-called GoF book Design Patterns (GoF referring to the Gang of Four -- Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides -- who authored it) has been very influential in software development--and rightfully so. Every programmer has read it or at least claims to have done so. In this article, I will explore how Design Patterns are used in Qt programming.
Design Patterns are a language-independent way of describing common patterns that occur again and again in programming. This is nothing new, patterns have been used for decennies, but the GoF book deserves the praise to have standardized how to describe patterns, give frequently used patterns names that developers all over the world refer to, and finally to have started a whole movement around patterns where developers identify new patterns and share them with each other. These names have become so common among software developers that it is not unusual to see magazine articles with titles like "How to Implement Observer in Programming Language X"-- it is not necessary to explain that Observer is the name of such a pattern, catalogized in the GoF book.
But let's leave the patterns aside for a moment and switch to Something Completely Different: the C++ GUI toolkit Qt. Qt is developed and marketed by the Norwegian company Trolltech and is available for Windows and various Unix dialects (and soon for the Macintosh). Qt follows the single-source paradigm: A program written with Qt can be compiled and run on any of the supported platforms without changes to the source code. Of course, reality sometimes trips you here, compiler bugs and incompatibilities (did I hear someone whispering MS Visual C++?) can make small adaptations necessary, and you cannot use any native API functions if you want to stay platform-independent. However, Qt still does a good job helping developers to write portable software. In recent years, Qt has become famous for forming the base of the KDE desktop, a free desktop for Unix systems that now is the default graphical desktop on most Linux distributions. Qt comes with extensive reference documentation; programmers' documentation is available by means of my O'Reilly book Programming with Qt, 2nd Edition.
It turns out that when developing with Qt, you follow quite a number of the patterns described in the GoF book, even if the code may look quite different from the code shown in the book. We'll look here at two of the patterns that commonly occur in Qt code and try to match them with the patterns in the GoF book.
Programming with Qt, 2nd Edition
Qt has the concept of signals and slots. This is a system that allows for component-based programming: Components can define signals that they emit under certain conditions and that have a defined list of parameters. Components can also define slots, which are nothing but ordinary C++ methods marked up with some preprocessor magic to be a slot. Signals and slots are brought together at runtime by means of the
QObject::connect() method, one of the central classes in Qt.
Let's take a small, canonical, example. A typical situation is to have a push button (likely to be labeled "OK") that is supposed to close a dialog it is in. A quick peek at the Qt reference documentation reveals that the class
QPushButton has a signal of
clicked(), which is emitted when the respective button it represents is clicked. Also, the class
QDialog has a slot
accept(), which closes the dialog with a positive response (while
reject() closes the dialog with a negative response). To bring these two together, the following code would be used:
QObject::connect( myPushButton, SLOT( clicked() ), myDialog, SLOT( accept() ) );
This says that whenever the push button is clicked (thus emitting its
clicked() signal), the slot
accept() of the dialog is invoked (and consequentially the dialog is closed). How this invocation actually occurs is well documented, but beyond the scope of this article.
Now to the patterns again. The GoF book describes an object-behavioral pattern called Mediator as:
Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
The example used in the GoF book contains a dialog as well, the Mediator object described there mediates the interaction between various elements in a dialog like a button, an entry field, and a listbox. This is exactly what we have in Qt, with the difference that in Qt, the
QObject class (mostly by means of its static fields) is the Mediator for everything that needs mediation (or, in Qt lingo, connections). The GoF Mediator pattern also involves
Colleagues, the classes between which mediation occurs. In Qt, these would be any classes that directly or indirectly inherit from
QObject and that therefore can participate in the signal/slot mechanism.
Notice, however, that the GoF Mediator pattern assumes that each
Colleague knows its mediator, while this is not even needed with Qt. Therefore, Qt provides for even more decoupling. Of course, it is possible to follow the original Mediator pattern more closely in Qt by creating a special Mediator class, which in its constructor sets up all the Qt connections and disconnects the
Colleagues again in its destructor.
The signal/slot mechanism even instantiates another GoF pattern, the Observer pattern, which is described as:
Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
We'll find this again in Qt if we consider that each signal can be connected to many slots (or to none at all). Each slot can also be connected to many signals, so we actually can have a many-to-many dependency here, if we wish, but one that is much easier to maintain that typical many-to-many-dependencies that involve direct invocations.
As an example, let's look at our dialog again. Clicking the OK button should usually not just close the dialog but also update the application state with the changed values in the dialog fields (while the Cancel button just closes the dialog without updating the application state). While it is certainly possible to do this update in the dialog directly (typically in an additional, user-defined slot), it is sometimes preferable to do these updates in the application code itself to avoid exposing too much of the internal structure of the application to the dialog. (On the other hand, this means that the structure of the dialog must be exposed to the application--there are always trade-offs!) This updating could be done in a user-defined slot; let's call it
updateState() here. Then, we would want two connections from the
clicked() signal of the push button:
QObject::connect( myPushButton, SLOT( clicked() ), myDialog, SLOT( accept() ) ); QObject::connect( myPushButton, SLOT( clicked() ), myApplication, SLOT( updateState() ) );
Thus, we have two objects here, the dialog object and the application object both observing the button for clicks.
You will discover other GoF patterns in Qt programming, like Singleton, Iterator, Command, and Flyweight, which are sometimes pretty obvious to find, showing that the Qt people have thought things through.
To conclude, the Design Patterns described in the GoF book also apply to Qt programming, which should not be surprising, given that Qt follows good software engineering practice--sometimes with an unusual syntax.
Matthias Kalle Dalheimer works as an independent author, translator and software consultant in Northern Germany, where he lives in a tiny village with his wife and son.
O'Reilly & Associates will soon release (January 2001) Programming with Qt, 2nd Edition.
You can also look at the Full Description of the book.
For more information, or to order the book, click here.
Return to ONLamp.com.