L'exemple de Hello World est base sur une programmation par evenement. Il me semble que wxWidget supporte aussi une programmation par signaux, comme sous Qt ou Gtk mais cela n'est pas mis en evidence : la totalite de la documentation semble basee sur le concept d'evenement.
La programmation par evenement est beaucoup moins souple que les signaux/slot facon Qt, car un evenement ne peut contenir que tres peu d'information: "il se passe qqch". Du coup, pour transporter des informations complexes, on est obliger de rajouter du code qui va chercher l'information la ou elle est. Les exemples simples comme le hello world ne mettent pas en evidence ce probleme, mais quand on rentre dans des cas reels, le flux de communication de donnees a l'interieur de l'application devient complexe. Avec une programmation purement evenementielle comme wxWidgets (ou MFC), ce flux est penible a gerer.
La communication par signaux permet de completement separer la preparation des donnees de leur traitement, et d'offrir plus d'independance entre les differents objets d'une application. Pour ceux qui sont familiers avec les Design Pattern, c'est un probleme recurrent resolu par le Mediateur. Pour faire communiquer un objet A avec un objet B, soit B doit connaitre A, soit A doit connaitre B, soit il y a un objet C entre A et B qui connait A et B. On peut dans ce cas faire evoluer A separement de B a condition de faire suivre C, qui devient le mediateur.
Ce qui est fort avec Qt, c'est que pour les cas simples, le mediateur, c'est la connection signal/slot et qu'il n'y a donc pas besoin de pondre une classe pour ca.
Je donne un exemple simpliste mais concret: une interface graphique doit generer des rectangles dont l'utiisateur controle la hauteur et la largeur. Il entre deux nombres de facon diverses, clique sur un bouton et un rectangle doit etre genere.
Cote interface, il y a donc du code qui doit lire deux nombre. On imagine deux spinbox et un bouton pour generer le tout. De l'autre cote, il y a une partie du programme qui recoit le "signal" de generer un nouveau rectangle.
En Qt, ca donnerait qqch dans le style:
class MyRectangleInput : public QVBox {
Q_OBJECT
public:
MyRectangleInput( QWidget * parent ) {
spin1 = new QSpinBox( this );
spin2 = new QSpinBox( this );
QPushButton * button = new QPushButton("Generate Rectangle");
connect( button, SIGNAL(clicked), this, SLOT(slotGenerate) );
}
slots:
void slotGenerate() {
emit generateRectangle( spin1->value(), spin2->value() );
}
signals:
void generateRectangle( int width, int height );
protected:
QSpinBox *spin1, *spin2;
};
De l'autre cote, on a la classe reception :
class RectangleGenerator : public QWidget
{
// [...]
public slots:
slotGenerateRectangle( int width, int height ) {
// create new rectangle
}
};
Pour faire marcher le tout, il doit y avoir un moment donne une connection entre une instance de MyRectangleInput et RectangleGenerator:
connect( myRectangleInstance, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstance, SLOT(slotGenerateRectangle(int,int)) );
On voit bien que les deux classes sont completement independantes, et ce qui les lie, c'est la connection signal / slot.
[ A noter que le MyRectangleInput peut etre librement redimensionner et que les widget adapteront la bonne taille ]
Si on revient a un toolkit base sur des evenements, on aurait une problematique differente. Je ne connais pas bien wxWidget, mais en reprenant le concept general de la haut, la classe A, c'est le MyRectangleInput, et la classe B, c'est le RectangleGenerator. Dans le cas ou on appuie sur le bouton, un evenment GENERATE_RECTANGLE est envoye. De la, il y a le choix:
- B connait A : la classe RectangleGenerator dispose d'un pointeur vers la classe MyRectangleInput et va lire elle-meme le contenu des spinbox. Le probleme, c'est que si on remplace les spinbox par autre chose, il faut faire evoluer aussi la classe RectangleGenerator. Un autre probleme qui est beauoup plus contraignant, c'est que RectangleGenerator doit disposer d'un pointeur sur MyRectangleInput. Que se passe-t-il si il y a plusieurs MyRectangleInput ? Il faut gerer la vie du pointeur. Que se passe-t-il si une autre partie de l'application veut effacer un pointeur sur MyRectangleInput ? Elle doit en informer toutes les classes qui possedent un pointeur sur MyRectangleInput. Plutot lourd.
- A connait B : cette fois, c'est MyRectangleInput qui connait bien RectangleGenerator, qui appelle directement l'instance avec les bons parametres qu'il va chercher en interne. On peut donc faire evoluer MyRectangleInput facilement. Mais on retombe vite dans une problematique similaire : imaginons qu'on aie plusieurs destinataires du message GENERATE_RECTANGLE. Ils doivent tous s'enregistrer aupres de MyRectangleInput et tous avoir une interface commune. Si en plus on a plusieurs generateurs, on arrive vite a une situation ou il y a n generateurs, n recepteurs et on doit maintenir n x n liens tout en sachant a tout moment que faire si des pointeurs sont effaces.
- C connait A et B : c'est pour ca qu'on a besoin d'une classe supplementaire C. Elle va recevoir l'evenement GENERATE_RECTANGLE, aller cherche l'info dans A parce qu'elle connait bien l'implementation de A, puis dispatcher cette information dans B. Si il y a plusieurs RectangleGenerator et plusieurs MyRectangleInput, la class C maintient la liste des associations et est la seule classe qui doit etre au courant quand un nouveau pointeur est cree ou supprime.
L'avantage dans le modele signaux/slot, c'est qu'on a pas besoin de classe C. Elle est deja dans le toolkit. Dans les problematiques que j'ai cite, on peut imaginer en QT :
connect( myRectangleInstance1, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceA, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance1, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceB, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance2, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceA, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance2, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceB, SLOT(slotGenerateRectangle(int,int)) );
si on efface un pointeur myRectangleInstanceX, il n'y a rien de special a faire, la connection disparait toute seule. Idem si on efface un rectangleGeneratorX.
Ce probleme peut paraitre simpliste, mais dans une application graphique (et meme non graphique), on a beaucoup de morceaux d'applications a faire communiquer ensemble (typiquement, le frontend et le backend) et les facilites fournies par Qt permettent de gerer cela avec beaucoup de legerete et d'elegance.
Avec une programmation par evenement, le code devient vite abominable et on est tout de suite obliger de faire appel a moultes Design Pattern pour garder un truc maintenable.
[^] # Re: Je me marre
Posté par Philippe F (site web personnel) . En réponse à la dépêche wxWidgets 2.6 est sorti. Évalué à 10.
La programmation par evenement est beaucoup moins souple que les signaux/slot facon Qt, car un evenement ne peut contenir que tres peu d'information: "il se passe qqch". Du coup, pour transporter des informations complexes, on est obliger de rajouter du code qui va chercher l'information la ou elle est. Les exemples simples comme le hello world ne mettent pas en evidence ce probleme, mais quand on rentre dans des cas reels, le flux de communication de donnees a l'interieur de l'application devient complexe. Avec une programmation purement evenementielle comme wxWidgets (ou MFC), ce flux est penible a gerer.
La communication par signaux permet de completement separer la preparation des donnees de leur traitement, et d'offrir plus d'independance entre les differents objets d'une application. Pour ceux qui sont familiers avec les Design Pattern, c'est un probleme recurrent resolu par le Mediateur. Pour faire communiquer un objet A avec un objet B, soit B doit connaitre A, soit A doit connaitre B, soit il y a un objet C entre A et B qui connait A et B. On peut dans ce cas faire evoluer A separement de B a condition de faire suivre C, qui devient le mediateur.
Ce qui est fort avec Qt, c'est que pour les cas simples, le mediateur, c'est la connection signal/slot et qu'il n'y a donc pas besoin de pondre une classe pour ca.
Je donne un exemple simpliste mais concret: une interface graphique doit generer des rectangles dont l'utiisateur controle la hauteur et la largeur. Il entre deux nombres de facon diverses, clique sur un bouton et un rectangle doit etre genere.
Cote interface, il y a donc du code qui doit lire deux nombre. On imagine deux spinbox et un bouton pour generer le tout. De l'autre cote, il y a une partie du programme qui recoit le "signal" de generer un nouveau rectangle.
En Qt, ca donnerait qqch dans le style:
class MyRectangleInput : public QVBox {
Q_OBJECT
public:
MyRectangleInput( QWidget * parent ) {
spin1 = new QSpinBox( this );
spin2 = new QSpinBox( this );
QPushButton * button = new QPushButton("Generate Rectangle");
connect( button, SIGNAL(clicked), this, SLOT(slotGenerate) );
}
slots:
void slotGenerate() {
emit generateRectangle( spin1->value(), spin2->value() );
}
signals:
void generateRectangle( int width, int height );
protected:
QSpinBox *spin1, *spin2;
};
De l'autre cote, on a la classe reception :
class RectangleGenerator : public QWidget
{
// [...]
public slots:
slotGenerateRectangle( int width, int height ) {
// create new rectangle
}
};
Pour faire marcher le tout, il doit y avoir un moment donne une connection entre une instance de MyRectangleInput et RectangleGenerator:
connect( myRectangleInstance, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstance, SLOT(slotGenerateRectangle(int,int)) );
On voit bien que les deux classes sont completement independantes, et ce qui les lie, c'est la connection signal / slot.
[ A noter que le MyRectangleInput peut etre librement redimensionner et que les widget adapteront la bonne taille ]
Si on revient a un toolkit base sur des evenements, on aurait une problematique differente. Je ne connais pas bien wxWidget, mais en reprenant le concept general de la haut, la classe A, c'est le MyRectangleInput, et la classe B, c'est le RectangleGenerator. Dans le cas ou on appuie sur le bouton, un evenment GENERATE_RECTANGLE est envoye. De la, il y a le choix:
- B connait A : la classe RectangleGenerator dispose d'un pointeur vers la classe MyRectangleInput et va lire elle-meme le contenu des spinbox. Le probleme, c'est que si on remplace les spinbox par autre chose, il faut faire evoluer aussi la classe RectangleGenerator. Un autre probleme qui est beauoup plus contraignant, c'est que RectangleGenerator doit disposer d'un pointeur sur MyRectangleInput. Que se passe-t-il si il y a plusieurs MyRectangleInput ? Il faut gerer la vie du pointeur. Que se passe-t-il si une autre partie de l'application veut effacer un pointeur sur MyRectangleInput ? Elle doit en informer toutes les classes qui possedent un pointeur sur MyRectangleInput. Plutot lourd.
- A connait B : cette fois, c'est MyRectangleInput qui connait bien RectangleGenerator, qui appelle directement l'instance avec les bons parametres qu'il va chercher en interne. On peut donc faire evoluer MyRectangleInput facilement. Mais on retombe vite dans une problematique similaire : imaginons qu'on aie plusieurs destinataires du message GENERATE_RECTANGLE. Ils doivent tous s'enregistrer aupres de MyRectangleInput et tous avoir une interface commune. Si en plus on a plusieurs generateurs, on arrive vite a une situation ou il y a n generateurs, n recepteurs et on doit maintenir n x n liens tout en sachant a tout moment que faire si des pointeurs sont effaces.
- C connait A et B : c'est pour ca qu'on a besoin d'une classe supplementaire C. Elle va recevoir l'evenement GENERATE_RECTANGLE, aller cherche l'info dans A parce qu'elle connait bien l'implementation de A, puis dispatcher cette information dans B. Si il y a plusieurs RectangleGenerator et plusieurs MyRectangleInput, la class C maintient la liste des associations et est la seule classe qui doit etre au courant quand un nouveau pointeur est cree ou supprime.
L'avantage dans le modele signaux/slot, c'est qu'on a pas besoin de classe C. Elle est deja dans le toolkit. Dans les problematiques que j'ai cite, on peut imaginer en QT :
connect( myRectangleInstance1, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceA, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance1, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceB, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance2, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceA, SLOT(slotGenerateRectangle(int,int)) );
connect( myRectangleInstance2, SIGNAL(generateRectangle(int,int)), rectangleGeneratorInstanceB, SLOT(slotGenerateRectangle(int,int)) );
si on efface un pointeur myRectangleInstanceX, il n'y a rien de special a faire, la connection disparait toute seule. Idem si on efface un rectangleGeneratorX.
Ce probleme peut paraitre simpliste, mais dans une application graphique (et meme non graphique), on a beaucoup de morceaux d'applications a faire communiquer ensemble (typiquement, le frontend et le backend) et les facilites fournies par Qt permettent de gerer cela avec beaucoup de legerete et d'elegance.
Avec une programmation par evenement, le code devient vite abominable et on est tout de suite obliger de faire appel a moultes Design Pattern pour garder un truc maintenable.