Modèle de conception : La délégation dans Cocoa. Principe et mise en pratique.

Sylvain Gamel

Cocoa s'appuie sur le langage Objective-C qui, contrairement au C++, ne propose pas un modèle de classe avec héritage multiple. L'héritage simple est la règle, comme dans le monde Java.

Si le langage propose des solutions techniques pour contourner les limites de l'héritage en introduisant l'idée de protocoles (interfaces en Java), le socle Cocoa s'appuie sur une solution conceptuelle : la délégation.

Ainsi, au lieu de dériver une classe NSApplication ou NSWindow pour l'enrichir de comportements spécifiques à votre application, Cocoa vous propose d'implémenter les comportements spécifiques dans un objet délégué.

- Titre : Modèle de conception : La délégation dans Cocoa. Principe et mise en pratique.

- Auteur : Sylvain Gamel

- Parution : 19 mai 2008

- Mise à jour : 23 avril 2020

- Licence : Le contenu de cet article a été rédigé par Sylvain Gamel et est disponible sous la licence Creative Commons "Paternité Pas d'Utilisation Commerciale" Par contre, les logos developpez.com, en-tête, pied de page, css et look & feel de l'article sont eux sous copyright 2008 developpez.com

Sommaire

- I. Principe de la délégation

- II. Mise en œuvre d'un délégué

- III. Perspective avec Java/Swing

- IV. Conclusion

I - Principe de la délégation

Certaines classes du socle Cocoa permettent de définir un délégué pour la gestion des comportements particuliers.
Ce modèle de conception est défini comme la délégation.

Ce délégué est une propriété de la classe qui est nommée delegate. Les méthodes associées suivent les conventions classiques.

Accesseur

- (id) delegate ;

Modificateur

- (void) setDelegate : (id) anObject;

Le fonctionnement de la délégation est simple.

1. Lorsque l'objet reçoit un message, il vérifie que son délégué ne propose pas une implémentation spécifique. Si c'est le cas, le message est transféré au délégué qui prend en charge son traitement.

2. Si le délégué ne prend pas en charge le message, l'objet utilise son implémentation par défaut pour la gestion du message.

Chaque classe qui implémente le modèle de délégation précise dans sa documentation la liste des méthodes qu'un délégué peut implémenter.

C'est toute la différence avec l'implémentation d'une interface en Java. Le délégué ne doit pas obligatoirement implémenter toutes les méthodes d'un protocole. Il se contente de proposer une implémentation pour les méthodes qu'il souhaite spécialiser. On dispose ainsi de la souplesse de l'héritage sans avoir la contrainte imposée par les protocoles formels.

II - Mise en œuvre d'un délégué

Pour les classes graphiques, la mise en œuvre d'un délégué est simple.

Il faut commencer par définir une classe qui va proposer des implémentations spécifiques de méthodes. Cette classe est ensuite liée graphiquement dans Interface Builder. Cette partie vous guidera pas à pas.

Pour commencer, créez un projet Cocoa de type « Cocoa Application ».

Une fois le projet créé, ajoutez une nouvelle classe que nous pouvons appeler AppDelegate. Cette classe va pouvoir être utilisée comme délégué de la fenêtre principale de notre application ainsi que comme délégué de l'application elle-même.

Vérifiez que tout va bien en compilant le projet.

Passez maintenant dans Interface Builder en ouvrant le fichier NIB de l'application.

Rajoutez une instance de votre classe dans l'application en faisant glisser un NSObject de la palette Library vers la fenêtre du document NIB. Renommez-le en delegate.

Ouvrez l'inspecteur pour définir la classe de votre instance et utiliser le nom de votre classe déléguée : AppDelegate.

Nous avons maintenant une instance de notre classe de délégation qui sera créée au démarrage de l'application. Vous allez définir le lien de délégation de l'objet application vers votre objet delegate.

Sélectionnez l'objet Application et affichez les connexions dans l'inspecteur. Tirez maintenant un lien de l'attribut delegate vers l'objet delegate situé dans le NIB.

L'inspecteur affiche maintenant votre objet delegate comme valeur de l'attribut delegate de l'application.

Faites de même pour la fenêtre de votre application.

Vous pouvez maintenant quitter Interface Builder et revenir dans XCode pour ajouter les méthodes suivantes dans l'interface de notre classe.

- Délégation pour l'application

- applicationWillFinishLaunching appelée au moment où l'application va effectivement démarrer.

- applicationShouldTerminateAfterLastWindowClosed qui permet de définir si l'application doit quitter lorsque sa dernière fenêtre est fermée.

- Délégation pour la fenêtre

- windowWillClose est appelé juste avant la fermeture d'une fenêtre.

Les signatures de messages à intégrer dans le fichier en-tête de votre classe :

@interface AppDelegate : NSObject {
// Pas d'attributs pour l'instant
}

// NSWindows delegation

- (void) windowWillClose: (NSNotification *) notification;

// NSApplication delegation

- (void) applicationWillFinishLaunching:
(NSNotification *) aNotification;

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:
(NSApplication *) theApplication;

@end

Une fois l'interface définie, il vous suffit de basculer dans l'implémentation de la classe pour ajouter le corps des méthodes.

@implementation AppDelegate

- (void) windowWillClose: (NSNotification *) notification
{
NSLog(@"Yep, delegate was notified that window will be closed. Quitting application.");
}

- (void) applicationWillFinishLaunching: (NSNotification *) aNotification
{
NSLog(@"Application will be launched now.");
}

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:
(NSApplication *) theApplication
{
NSLog(@"applicationShouldTerminateAfterLastWindowClosed ?");

return YES;
}

@end

Compilez et exécutez votre application. En affichant la console, vous devriez voir les messages apparaître.

Vous pouvez maintenant jouer un peu en débranchant votre classe soit de l'application, soit de la fenêtre pour apprécier la différence de comportement.

Le projet complet est joint à cet article. Vous pouvez le télécharger pour tester rapidement.

Project SimpleDelegate : Le code source de l'exemple sous la forme d'un projet XCode 3.

III - Perspective avec Java/Swing

Cocoa met en avant le modèle de la délégation.

Du côté Java/Swing on s'appuie davantage sur un modèle plus évènementiel qui utilise les écoutes (listener) sur évènements.

- La classe peut se voir ajouter autant de listeners que nécessaire.

- Chaque objet en écoute est spécialisé sur un type d'évènements particuliers.

- Les listeners sont décrits par des interfaces (comprendre protocoles) et une implémentation doit être fournie.

- Afin de limiter le nombre de méthodes à implémenter dans le code spécialisé, une classe abstraite d'adaptation fournit une implémentation vide pour les méthodes de l'interface.

On se retrouve ainsi pour un type d'évènements donnés avec :

Type d'évènement

Xxx

Description d'évènement

XxxEvent

Interface d'écoute

interface XxxListener

Adapteur

class XxxAdapter

Certes ce modèle permet le typage statique des données au prix d'une relative lourdeur conceptuelle puisque pour gérer un évènement, on doit au minimum dériver la classe d'adaptation ou implémenter l'interface d'écoute.

Le nombre d'objets mis en œuvre n'est pas forcément différent, mais la facilité d'écriture n'est pas la même.

Preuve en est, à la suite de l'introduction de Swing, Java s'est vu enrichi d'un moyen de déclarer en ligne une classe anonyme. Ce n'est peut-être pas le meilleur moyen de structure une application.

IV - Conclusion

Le modèle de la délégation est largement utilisé dans Cocoa pour spécialiser les comportements d'une classe existante.

Ce modèle s'appuie sur le typage dynamique du langage en évitant d'imposer l'utilisation de protocoles formels. Les objets qui spécialisent une classe, ou qui proposent un comportement spécifique peuvent être ainsi allégés et se concentrer sur l'essentiel.

Le faible couplage entre les objets/vues et les implémentations est aussi un moyen de garantir une bonne réutilisation des composants.