Les pièges du couplage

muqaddarmuqaddar Administrateur
février 2007 modifié dans API AppKit #1
Dans cet article, deftones_chrix fournit une description du couplage de classes, voux expose les problèmes qu'il peut engendrer, et propose des solutions pour l'éviter. Pour attirer votre curiosité, sachez que le couplage de classes en programmation orientée objet (POO) est le fait de rendre une de vos classes dépendante d'une autre.
Attention, le niveau requis pour cet article est "confirmé". Bonne lecture studieuse.

Réponses

  • muqaddarmuqaddar Administrateur
    22:53 modifié #2
    Kézako que le couplage ?

    Le couplage en programmation-objet est le fait de rendre une classe dépendante d'une autre. En d'autres termes, une classe A ne peut être correctement implémentée dans un projet sans la classe B.

    Cette implémentation supplémentaire peut entraà®ner une complexité non désirée, voire pire, empêcher l'intégration de la Classe B dans le projet 2.

    Voici des exemples de couplage et les solutions pour l'éviter.

    [Fichier joint supprimé par l'administrateur]
  • muqaddarmuqaddar Administrateur
    22:53 modifié #3
    Présentation de ce qui est désiré

    Imaginons que nous souhaitons avoir une application qui permette d'afficher des informations par un browser et qu'à  chaque clique sur un élément, une action (appelons la ActionT1) se réalise. L'action réalisée sera l'affichage dans la fenêtre de débogage du chemin de l'élément sélectionné.

    Nous allons utiliser pour notre projet :
    - Une instance myBrowser d'une sous-classe de NSBrowser. Notre sous-classe s'appelle MYBrowser et nous lui définissons un attribut objectAction et surchargeons la méthode -(void)doClick:(id)sender.
    - Une instance IA de la Classe A qui devra réaliser l'ActionT1 lors du click sur un élément du browser.

    NOTE : Il serait possible définir IA comme le delegate de l'instance de MYBrowser. Mais pour ne pas être dans ce cas particulier, nous définissons l'attribut actionObject qui représente l'objet devant réaliser l'action.

    À première vue, nous avons donc l'organisation suivante :

    Attributs présent dans la description de Classe A ne sera pas utilisé. Il est juste présent pour faire comme si :)

    [Fichier joint supprimé par l'administrateur]
  • muqaddarmuqaddar Administrateur
    22:53 modifié #4
    Suite de notre exemple

    Affectation de l'attribut objectAction

    "myBrowser" utilise IA comme objet devant réaliser l'action (objectAction). Le premier piège à  éviter est d'oublier (je vise ceux qui sont habitués au C ou C++) la capacité dynamique de l'objective-C.

    Ci-dessous une déclaration de l'interface ne prenant pas en compte le côté dynamique du langage :

    @interface MYBrowser : NSBrowser<br />{<br />&nbsp; &nbsp; ClasseA *_objectAction;<br />}
    


    On s'aperçoit qu'une telle déclaration, oblige le développeur à  faire un :

    #import &quot;ClasseA.h&quot;
    


    Et voilà  à  peine commencé que nous obligeons tout projet qui utilisera la classe MyBrowser à  avoir comme objectAction une instance de ClasseA !

    Il est donc largement préférable d'utiliser le type "id" comme objectAction :

    @interface MYBrowser : NSBrowser<br />{<br />&nbsp; &nbsp; id _objectAction;<br />}
    


    Avec cette version, l'import précédent n'est plus nécessaire. Pour l'affectation à  proprement dite, nous définirons les 2 méthodes suivantes :

    - (void) initWithObjectAction :(id)o
    


    et

    - (void) setObjectAction :(id)o
    


    Cet objet sera récupéré par la méthode : – (id) objectAction


    [Fichier joint supprimé par l'administrateur]
  • muqaddarmuqaddar Administrateur
    22:53 modifié #5
    NOTE : Dans le diagramme de classe, j'utilise objectAction alors que dans mes exemples de codes, j'utilise _objectAction. L'underscore est utilisé par convention de développement pour signaler que seul le développeur de la classe a le droit d'utiliser cette variable directement. Les autres développeurs intégrant cette classe doivent passer par les " accessors ".

    Surcharge de la méthode doClick

    Revenons à  notre objectif : nous voulons que lors d'un clic sur un élément de myBrowser, nous exécutions la méthode doActionT1 de l'objet IA. On imagine alors que la méthode doClick est de cette forme :

    - (void)doClick:(id)sender<br />{<br />&nbsp; &nbsp; [super doClick :sender] ;<br />&nbsp; &nbsp; [_objectAction doActionT1] ;<br />}<br />
    


    La méthode doActionT1 doit afficher le chemin de l'élément sélectionné et vu son appel dans doClick, on en déduit qu'elle sera de la forme suivante :

    - (void)doActionT1 ;
    


    Avec une telle déclaration, nous retombons dans le piège d'un couplage fort. En effet, comment récupérer l'élément sélectionné dans myBrowser ? Définir une variable d'instance (ou bien encore un IBOutlet) pour qu'il pointe sur myBrowser ? Hum... cela est-il vraiment intéressant d'avoir une telle déclaration ? Sans compter que pour toute réutilisation de ClasseA, il faudra initialiser cette variable avec le browser utilisé. Très moyen et la réutilisation s'annonce hasardeuse.

    En effet, que se passerait s'il fallait réintégrer ClasseA dans un projet ayant plusieurs objets MYBrowser ? Comment savoir de quel objet myBrowser est venu l'appel ?

    Pour éviter ce genre d'inconvénient, le browser doit être vu comme un paramètre de la fonction doActionT1. Nous obtenons ainsi :

    - (void)doActionT1 :(id)browser ;
    


    et l'appel dans myBrowser se fait comme ceci :
    <br />- (void)doClick:(id)sender<br />{<br />&nbsp; &nbsp; [super doClick :sender] ;<br />&nbsp; &nbsp; [_objectAction doActionT1 :self] ;<br />}
    


    De cette façon, nous nous passons de variables d'instance dans ClasseA servant seulement à  indiquer le browser à  utiliser. Ce qui nous libère d'initialisations fastidieuses et évite de lier les instances de ClasseA avec un browser donné.

    NOTE : Pour ne pas compliquer et surcharger les fonctions, je pars du principe que tout objet indiqué par objectAction connaà®t la fonction doActionT1. Dans le cas où cela n'est pas toujours vérifié, il ne faudra pas oublier de mettre à  la place la vérification de réponse aux messages.
  • muqaddarmuqaddar Administrateur
    22:53 modifié #6
    Suite de la suite

    À première vue, tout est enfin bien défini avec le minimum de couplage. Mais.... imaginons que notre classe MYBrowser puisse être utilisée pour réaliser d'autres actions sur une instance de ClasseA qui dépend de l'instance de MYBrowser. Par exemple, on ajoute l'objet mySecondBrowser qui fait une tout autre action lorsque l'on clique sur un de ses éléments.

    Vu la surcharge de la méthode doClick, il est possible de créer une autre sous classe de NSBrowser gérant cette action. Cela nous amène à  avoir 2 versions de MYBrowser à  maintenir et à  gérer. Solution loin d'être glop :(

    Une autre possibilité est de déterminer l'action à  réaliser en fonction du browser envoyant le message.
    Pour cela nous allons donc modifier encore une fois la méthode doClick :

    - (void)doClick:(id)sender<br />{<br />&nbsp; &nbsp; [super doClick :sender] ;<br />&nbsp; &nbsp; [_objectAction doActionTAll :self] ;<br />}
    


    [Fichier joint supprimé par l'administrateur]
  • muqaddarmuqaddar Administrateur
    22:53 modifié #7
    La gestion de l'action à  réaliser se fait donc dans la méthode doActionTAll. L'erreur fréquente serait de remettre en place les variables d'instances, chacune de ces variables pointant sur un objet myBrowser.
    <br />- (void)doActionTAll :(id)browser<br />{<br />// mauvais exemple<br />&nbsp; &nbsp; if (browser == _browser1) [self doActionT1 :browser]<br />&nbsp; &nbsp; else if (browser == _browser2) [self doActionT2 :browser]<br />}
    


    L'utilisation de variables d'instance a très peu d'intêret si elles ne servent qu'à  connaà®tre l'objet passé en paramètres (sans compter que cela alourdit l'interface de ClasseA).

    L'astuce est de définir un attribut à  la classe MYBrowser comme le tag par exemple (mais on est libre de choisir un autre nom) qui permet d'affecter une valeur unique à  chaque objet de cette classe. Ce tag est d'ailleurs présent dans certaines classes de Cocoa :)

    Nous obtenons donc :
    <br />- (void)doActionTAll :(id)browser<br />{<br />&nbsp; &nbsp; if ([browser tag] == 1) [self doActionT1 :browser]<br />&nbsp; &nbsp; else if ([browser tag] == 2) [self doActionT2 :browser]<br />}
    


    Au final, le diagramme de classes a cette tête :

    [Fichier joint supprimé par l'administrateur]
  • muqaddarmuqaddar Administrateur
    22:53 modifié #8
    Conclusion

    Il n'est pas toujours possible d'éviter le couplage entre différentes classes, mais souvent le couplage est le résultat d'une mauvaise analyse ou d'une volonté d'aller vite. Le problème est que quelquefois en voulant gagner 10 minutes, on génère des classes qui seront difficiles à  réutiliser et les 10 minutes gagnées peuvent se transformer en de nombreuses de perdues.
Connectez-vous ou Inscrivez-vous pour répondre.