PDA

View Full Version : Application architecture



m15ch4
4th February 2011, 12:46
Hello!

I have question about architectures of applications you write.

When I create new Qt application using QtCreator it creates main.cpp, mainwindow.h/cpp and other files. MainWindow object is created in main.cpp. When I write another classes (eg. for db or hardware access) I would like to separate them from MainWindow class (objects of "other" classes are not created in MainWindow). So their instances (I think) should be outside of MainWindow class but I'm not sure main.cpp is good place for this. Simultaneously I would like to send signals between GUI and other objects.

I am curious how you organize your applications. I realize that it depends on purpose of application and its complexity but some diagrams would be very useful.

Thanks.

high_flyer
4th February 2011, 13:39
When I write another classes (eg. for db or hardware access) I would like to separate them from MainWindow class (objects of "other" classes are not created in MainWindow). So their instances (I think) should be outside of MainWindow class but I'm not sure main.cpp is good place for this.
If I understand you correctly, then it seems you are confusing things.
Its true that FUNCTIONALITY should be encapsulated (be it classes, methods or other scopes).
Then you talk about separation.
But if you need to access what ever functionality, then you should and must have access to the object that encapsulates that functionality.
So you very well might need objects that encapsulate hardware access (for example) in your GUI.

Is this your first project in C++?

m15ch4
4th February 2011, 20:03
Thanks for your reply.

No. It is not my first project. But I have problem with writing applications which can be easily modified.
Is it normal that objects responsible for some functionalities are created in GUI objects?

kornicameister
4th February 2011, 20:16
what you're asking about can be found here
http://doc.qt.nokia.com/4.7-snapshot/model-view-programming.html

one basic rule is to extract all code interacting with data from the qui code :)
and yes, you need to create an object which can be describe as a bridge between gui and data itself

m15ch4
4th February 2011, 20:55
Could you give some simple example?

stampede
4th February 2011, 21:48
Could you give some simple example?
Lets say you have some entities stored in xml file. To read and modify those files you can create a class (pseudocode):

class FileParser{
public:
List<Data> readData( string file );
void saveData( List<Data>, string out_file );
};

The only responsibility of this class is to hide all the details associated with Data storage in your files and provide an interface to access them.
Now, maybe you'll want to display some data in a ListWidget, no problem, you have method to access a list of entities stored in a file ( List<Data> readData(file) ), so you can create a method:

void ListWidget::displayData( file ){
FileParser parser;
List<Data> data = parser.readData(file);
foreach( Data d, data ){
this->displayEntity(d);
}
or you can create a function that prints the data to stdout somehow.

There could be, for example, a HardwareMonitor that you use to read CPU temperature instead of file parser:

class HardMonitor{
public:
int cpuTemperature();
private:
HardwareMonitorImpl _impl; //!< this object can hide the details of implementation for different operating systems
};

If you want to display CPU temp in your GUI you wont escape creating a HardMonitor object (same for previous example). But the fact that details of accessing underlying data are hidden makes your application more flexible, eg. it wont hurt that much if you want to replace xml storage schema with some other data format, or provide new way to get CPU temperature. Client classes that uses FileParser or HardMonitor wont be affected by this change, as the only thing they see is the interface.

I hope this will be of any use for you.

kornicameister
4th February 2011, 21:53
example could be simple computing or data processing
for example you click the button by which you request for data from the database and you expect this data to be displayed with some funky graphical stuff
to do so you a class which will do this job
so you encapsulate all the functionality you need in the class and what's left is to call suitable method inside the gui which will process/return formatted date

stampede's example is better, I just do not know how to explain something :D sometimes :)

m15ch4
5th February 2011, 21:56
Thanks for your replies!

I understand that there should be separate classes for GUI, for data access and data processing. That's clear for me.

But as usual I have another question. Because the object responsible for the GUI is created in the "main.cpp" file, should I treat it as a base object, and create objects responsible for data processing in GUI objects (and in objects responsible for data processing create objects of data access layer)? Or maybe objects of different layers (GUI, data processing, data access, ...) should exist side by side?
Which approach is better?

d_stranz
5th February 2011, 22:42
I started off programming in the Microsoft MFC environment, and probably spent close to 15 years doing that before being saved by Qt. However, some things from that experience got stuck because they are reasonably good solutions to architecture problems.

First, I always derive my own app class from QApplication. This class holds any information which is global to the entire app. For example, most of the applications I write are chemistry-based, so the App class holds the global instance of the Periodic Table that is used in all chemical formula calculations. Many of my apps are licensed, so I have a QLicensedApp class, derived from QApplication, that does all that for me and serves as the base class of the actual App class.

Second, the App class always creates at least one Document class. This class is also sort of global, since it lives in the App class, but unlike the other information it can be destroyed and replaced when the user chooses "File->New" from the menu. The Document class is usually derived from QObject so it can use signals and slots.

Third, all information maintained by the Document class is pure C++ (*not* Qt). I use STL containers, boost smart pointers, etc. so that the algorithms and other data used in the document can be moved to any other standard C++ environment, Qt or not. It is because all of the core algorithms and data structures I use are in this standard C++ that I was able to immediately switch from Microsoft MFC to Qt *without* having to change any of it. My coworkers are still using MFC, and we share all the same algorithms and data structures without changes.

Fourth, the Document communicates with the rest of the application GUI using signals and slots. This makes for a loose coupling between the Document and the GUI. The Document provides slots so the GUI can cause it to do things with the information it is storing. When that information is changed, it emits a signal. Usually a parameter to the signal is a pointer or const reference to the C++ object that was changed, so any slot listening for the signal knows nothing about the document contents.

Fifth, almost all GUI interaction runs through the MainWindow class. All menus, toolbars, context menus, etc. are based on QAction items created by the MainWindow. In addition, most of the rest of the GUI is constructed by the MainWindow, and signal and slot connections between the Document and the other GUI components are made in the MainWindow. This adds further decoupling. since the MainWindow ends up being the only GUI class that knows about the signals and slots of the Document. The other GUI classes know nothing about the Document, they are just listening on their slots or sending signals when the user does something.

Sixth, I use Model/View components extensively. For each collection of information stored in the Document, there is usually a Model that serves as the interface between the Document and the table, tree, or other widget that displays it. These Models are owned by the MainWindow. Typically, the Document emits a "collectionChanged( const SomeCollection & )" signal, and the Model listens for it on a matching slot. The Model stores the pointer to the collection that is passed by the Document in its signal, and serves it up via the Model's data() method.

With this basic architecture of App - Document(s) - MainWindow - Rest of the GUI, I have a very flexible way to build, modify, and extend applications, and to create GUI components that can be reused by other applications. If my customer says, "I'd rather see this information presented as a table instead of a tree", in most cases the only thing I have to change is the part of the GUI that creates a table instead of a tree. Maybe the model needs some fixing, too, but none of the rest of anything needs to be touched.

m15ch4
6th February 2011, 13:05
d_stranz:
If you do signal/slot connections in the MainWindow class then you must include the header files of your QLecenseApp and classes that receive signals in MainWindow.cpp. And vice versa. Am I right?

nish
6th February 2011, 17:23
instead of subclassing qapplication, why not have a seperate global class?

m15ch4
6th February 2011, 17:36
nish: Maybe because you can have access to QApplication through "qApp".

d_stranz
6th February 2011, 17:42
Actually, LicensedApp is only used in main.cpp. The MainWindow class does not know anything about the QApplication class. So, a typical main() would look like this:



int main(int argc, char *argv[])
{
MyApp app( argc, argv ); // derived from LicensedApp
if ( app.IsLicensed() )
{
// For a single-document app, MyApp has a MyDoc member variable
MyDoc * pDoc = app.GetDocument();

// Configure document with any startup parameters
// ...

// MainWindow gets built with the Doc pointer
MyMainWindow mainWindow( pDoc );
mainWindow.show();

app.connect( &app, SIGNAL( lastWindowClosed() ), &app, SLOT( quit() ) );

// Licensing uses a USB stick, so make sure the stick is always inserted
app.MonitorLicense();
retVal = app.exec();
}
else
QMessageBox::critical( 0, "Application - Fatal Error", "Unregistered or license not valid. Application cannot continue." );
}


So MainWindow.[h, cpp] does not include MyApp.h anywhere. However, it -does- need MyDoc.h because it needs to connect to MyDoc signals and slots. Since MainWindow is responsible for building the other parts of the GUI, it also needs to include header files for those other widgets, too. But those other widgets do not include MainWindow.h or MyDoc.h, since everything they know comes in from a slot or goes out through a signal. They don't know who is sending the signals to them, or who is receiving theirs.

So for example, if MyDoc has an array of data and that data gets changed somehow (maybe a menu item opens a file and reads it in, then the MainWindow sticks it into the document through a MainWindow::setData() signal / MyDoc:: onSetData() slot). The document does whatever it needs to do to update its internal state, then emits a MyDoc::dataChanged( const MyDataArray & ) signal.

And maybe one of the GUI windows has a table that displays that data. It needs a slot to listen for the dataChanged() signal. But it doesn't need to know anything about the document, all it needs to know about is "MyDataArray", so it includes the MyDataArray.h file.

For an analogy, think about the way spy networks operate. No one knows everything, because that would be a big security risk. So, the spy who is responsible for watching some target only knows he has to watch the target and report what he sees. He doesn't know why he watches the target, he just has his job. Each day, he writes his report and hides it under a rock in the park. Some other spy comes by in the night and removes the report and leaves the instructions for the next day.

The first spy doesn't know anything except "get instructions from under the rock" and "put reports in their place". The second spy only knows "get reports from under the rock" and "leave instructions in their place". Neither spy knows each other, and all communication is through the rock, so each spy needs to know how the rock works and nothing else.

The Spy #3 knows a little bit more, because he has set up the connection between Spy #1 and Spy #2 using the rock. So he tells Spy #1, "leave your reports and look for instructions under the rock near the water fountain in the park" and he tells Spy #2 "pick up reports and leave new instructions under the rock...". Spy #3 sends this information to Spy #1 using a different rock and to Spy #2 using a hole in the big tree.

In the end, each player needs to know only as much about the whole plot as he needs to perform his job. Mr. Big, who is running the network (and keeps all the information he is gathering in his Document), really doesn't know any of the other spies except for the ones he is in immediate contact with. And his communication with them is through signals like "New target needs watching" or slots like "New information received on Target X".

So in my case, each of the GUI windows I have generally deal with displaying or editing only a single data structure, so they need to include the header file that declares that data structure and its methods. The MainWindow sets up the communication between the Document and the GUI, so the GUI doesn't need to know about the Document or vice-versa. The MainWindow needs to know about both, of course, so it includes the header files for the Document and the GUI window. But if the information in the signals and slots is passed as pointers (*) or references (&), then it -doesn't- need the MyDataArray.h file. It just needs a declaration like "class MyDataArray;" since it isn't going to look at any of the information in the signal or slot, it's just making the connections.

nish
7th February 2011, 06:07
nish: Maybe because you can have access to QApplication through "qApp".
But then qApp needs to be casted to MyApp* everytime they need to use it. And MyApp.h need to included anyway. I dont see any benefit in this.


Actually, LicensedApp is only used in main.cpp. The MainWindow class does not know anything about the QApplication class.

If it only used in main() they why create an dependency on inheriting QApp? Application security deserves its own separate class, And you can use it in you MFC then.



So, a typical main() would look like this:



int main(int argc, char *argv[])
{
MyApp app( argc, argv ); // derived from LicensedApp
if ( app.IsLicensed() )
{
// Licensing uses a USB stick, so make sure the stick is always inserted
app.MonitorLicense();
retVal = app.exec();
}
else
QMessageBox::critical( 0, "Application - Fatal Error", "Unregistered or license not valid. Application cannot continue." );
}

I really hope that it is just an example main(). Because it just take a two byte patch to defeat this protection. :D

m15ch4
7th February 2011, 08:15
nish:

Perhaps you could introduce some example applications organization. It would be very helpful and would look at the problem from another perspective (especially if you include a bit of pseudocode).

Thanks for all answers.

nish
7th February 2011, 08:36
Perhaps you could introduce some example applications organization. It would be very helpful and would look at the problem from another perspective (especially if you include a bit of pseudocode).

Thanks for all answers.

The example you provided is very good and most of apps i worked on were using the similar approach.

Coming to the QApplication issue.
Inheriting from QApp should be done when you want to extend the original class's functionality. For example QApplication extends QCoreApplication to provide GUI.
Now lets suppose you app is requried to run in console mode, then you need to use QCoreApplication, which you cant because you are inheriting from QApplication.
Similarly you cannot use other Classes like QtSignleApplication (inherites from QApplication), because now you have to change your MyApp code to inherit from QtSignleApplication.

Furthurmore, you are designing you main Security class inherited from QApplication, which makes it Qt depended. All your serial generation/comparision etc should be independent of Qt to use in in any C++ as you mentioned.

I can explain why your protection in main() is easy to defeat, but i dont think they are implemented as such in your real program.

m15ch4
7th February 2011, 09:41
nish:
instead of subclassing qapplication, why not have a seperate global class?

Did you mean that instead of creating objects (MainWindow, DataProcess, DataAccess, ...) in the main.cpp file (or QApp class) we should create those objects in separate class (eg. Engine) which will be instantiated in main.cpp?

nish
7th February 2011, 17:41
not really. I already agreed that your design is good. Its just the case of Inheriting from QApp that is think should be revisited.

d_stranz
8th February 2011, 23:35
Furthurmore, you are designing you main Security class inherited from QApplication, which makes it Qt depended. All your serial generation/comparision etc should be independent of Qt to use in in any C++ as you mentioned.

I should make clear that all of the security functions -are- implemented independently of my LicensedApp class; this class -is- the Qt wrapper that gives access to the non-Qt security features and exists mainly to supply an event loop. These are in fact shared with non-Qt (e.g. MFC) apps.

m15ch4
9th February 2011, 10:41
d_stranz:

Sixth, I use Model/View components extensively. For each collection of information stored in the Document, there is usually a Model that serves as the interface between the Document and the table, tree, or other widget that displays it. These Models are owned by the MainWindow. Typically, the Document emits a "collectionChanged( const SomeCollection & )" signal, and the Model listens for it on a matching slot. The Model stores the pointer to the collection that is passed by the Document in its signal, and serves it up via the Model's data() method.

Do you write your own Model classes or you use those from Qt (like QAbstractItemModel)? If your data is stored in custom collection, then you probably have to reimplement at least data() method to fit your data organization (in SomeCollection).

I would be very thankful if you could explain this.