Le couplage avec des chaînes est-il plus lâche qu'avec les méthodes de classe?

18

Je démarre un projet de groupe scolaire à Java, en utilisant Swing. Il s'agit d'une interface graphique simple sur l'application de bureau de base de données.

Le professeur nous a donné le code du projet de l'année dernière afin que nous puissions voir comment il fait les choses. Mon impression initiale est que le code est beaucoup plus compliqué qu'il ne devrait l'être, mais j'imagine que les programmeurs pensent souvent cela en regardant du code qu'ils n'ont pas simplement écrit.

J'espère trouver des raisons pour lesquelles son système est bon ou mauvais. (J'ai demandé au professeur et il m'a dit que je verrais plus tard pourquoi c'est mieux, ce qui ne me satisfait pas.)

Fondamentalement, pour éviter tout couplage entre ses objets persistants, ses modèles (logique métier) et ses vues, tout se fait avec des chaînes. Les objets persistants qui sont stockés dans la base de données sont des tables de hachage de chaînes, et les modèles et les vues "s'abonnent" les uns aux autres, fournissant des clés de chaîne pour les "événements" auxquels ils s'abonnent.

Lorsqu'un événement est déclenché, la vue ou le modèle envoie une chaîne à tous ses abonnés qui décident quoi faire pour cet événement. Par exemple, dans l'une des méthodes d'écoute d'action des vues (je crois que cela définit simplement le BicycleMakeField sur l'objet persistant):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

Cet appel arrive finalement à cette méthode dans le modèle de véhicule:

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

Le professeur dit que cette façon de faire est plus extensible et plus facile à entretenir que le simple fait que la vue appelle une méthode sur un objet logique métier. Il dit qu'il n'y a pas de couplage entre les points de vue et les modèles parce qu'ils ne connaissent pas l'existence les uns des autres.

Je pense que c'est un pire type de couplage, car la vue et le modèle doivent utiliser les mêmes chaînes pour fonctionner. Si vous supprimez une vue ou un modèle ou effectuez une faute de frappe dans une chaîne, vous n'obtenez aucune erreur de compilation. Cela rend également le code beaucoup plus long que je ne le pense.

Je veux en discuter avec lui, mais il utilise son expérience de l'industrie pour contrer tout argument que moi, l'étudiant inexpérimenté, pourrais faire. Y a-t-il un avantage à son approche qui me manque?


Pour clarifier, je veux comparer l'approche ci-dessus, avec le fait que la vue soit évidemment couplée au modèle. Par exemple, vous pouvez passer l'objet de modèle de véhicule à la vue, puis pour modifier la "marque" du véhicule, procédez comme suit:

vehicle.make = bicycleMakeField.getText();

Cela réduirait 15 lignes de code actuellement utilisées UNIQUEMENT pour définir la marque du véhicule en un seul endroit, en une seule ligne de code lisible. (Et puisque ce type d'opération est effectué des centaines de fois dans l'application, je pense que ce serait une grande victoire pour la lisibilité et la sécurité.)


Mise à jour

Mon chef d'équipe et moi avons restructuré le cadre de la façon dont nous voulions le faire en utilisant la frappe statique, en avons informé le professeur et nous avons fini par lui faire une démonstration. Il est assez généreux pour nous permettre d'utiliser notre framework tant que nous ne lui demandons pas d'aide, et si nous pouvons tenir le reste de notre équipe au courant - ce qui nous semble juste.

Philippe
la source
12
OT. Je pense que votre professeur devrait se mettre à jour et ne pas utiliser l'ancien code. Dans le milieu universitaire, il n'y a aucune raison d'utiliser une version JAVA de 2006 à 2012.
Farmor
3
Veuillez nous tenir au courant lorsque vous aurez finalement sa réponse.
JeffO
4
Quelle que soit la qualité du code ou la sagesse de la technique, vous devez supprimer le mot "magie" de votre description des chaînes utilisées comme identificateurs. "Chaînes magiques" implique que le système n'est pas logique, et qu'il va colorer votre vue et empoisonner toute discussion que vous avez avec votre instructeur. Des mots comme «clés», «noms» ou «identificateurs» sont plus neutres, peut-être plus descriptifs et plus susceptibles de donner lieu à une conversation utile.
Caleb
1
Vrai. Je ne les ai pas appelés cordes magiques en lui parlant, mais vous avez raison, il n'est pas nécessaire de faire en sorte que le son soit pire qu'il ne l'est.
Philip
2
L'approche décrite par votre professeur (notifier les auditeurs via des événements appelés chaînes) peut être bonne. En fait, les énumérations pourraient avoir plus de sens, mais ce n'est pas le plus gros problème. Le vrai problème que je vois ici est que l'objet qui reçoit la notification est le modèle lui-même, puis vous devez envoyer manuellement en fonction du type d'événement. Dans un langage où les fonctions sont de première classe, vous enregistrez une fonction et non un objet à un événement. En Java, je suppose que l'on devrait utiliser une sorte d'objet enveloppant une seule fonction
Andrea

Réponses:

32

L'approche que propose votre professeur est mieux décrite comme typée de façon stricte et est erronée à presque tous les niveaux.

Le couplage est mieux réduit par l' inversion de dépendance , qui le fait par une répartition polymorphe, plutôt que de câbler un certain nombre de cas.

back2dos
la source
3
Vous écrivez "presque tous les niveaux", mais vous n'avez pas écrit pourquoi ce n'est pas (à votre avis) un cas approprié dans cet exemple. Serait intéressant de savoir.
hakre
1
@hakre: Il n'y a pas de cas appropriés, du moins pas en Java. J'ai dit que "ça ne va pas à presque tous les niveaux", parce que c'est mieux que de ne pas utiliser d'indirection du tout et de jeter tout le code en un seul endroit. Cependant, l'utilisation de constantes de chaînes magiques (globales) comme base pour une répartition manuelle n'est qu'une façon compliquée d'utiliser des objets globaux à la fin.
back2dos
Votre argument est donc: il n'y a jamais de cas appropriés, donc celui-ci n'en est pas un aussi? Ce n'est guère un argument et en fait pas très utile.
hakre
1
@hakre: Mon argument est que cette approche est mauvaise pour un certain nombre de raisons (paragraphe 1 - suffisamment de raisons pour éviter cela sont données dans l'explication du type string) et ne devraient pas être utilisées, car il y en a une meilleure (paragraphe 2 ).
back2dos
3
@hakre Je peux imaginer une situation qui justifierait cette approche serait si un tueur en série vous avait scotché sur votre chaise et tenait une tronçonneuse au-dessus de votre tête, riant de façon maniaque et vous ordonnant d'utiliser des littéraux de chaîne partout pour la logique de répartition. En dehors de cela, il est préférable de s'appuyer sur les fonctionnalités du langage pour faire ce genre de choses. En fait, je peux penser à un autre cas impliquant des crocodiles et des ninjas, mais c'est un peu plus compliqué.
Rob
19

Votre professeur se trompe . Il essaie de découpler complètement la vue et le modèle en forçant la communication à voyager via une chaîne dans les deux directions (la vue ne dépend pas du modèle et le modèle ne dépend pas de la vue) , ce qui va totalement à l'encontre du but de la conception orientée objet et du MVC. Il semble qu'au lieu d'écrire des cours et de concevoir une architecture basée sur OO, il écrit des modules disparates qui répondent simplement aux messages textuels.

Pour être un peu juste envers le professeur, il essaie de créer en Java ce qui ressemble beaucoup à l'interface .NET très courante appelée INotifyPropertyChanged, qui informe les abonnés des événements de changement en passant des chaînes qui spécifient quelle propriété a changé. Dans .NET au moins, cette interface est principalement utilisée pour communiquer à partir d'un modèle de données pour afficher des objets et des éléments d'interface graphique (liaison).

Si elle est bien faite , cette approche peut présenter certains avantages¹:

  1. La vue dépend du modèle, mais le modèle n'a pas besoin de dépendre de la vue. Il s'agit d'une inversion de contrôle appropriée .
  2. Vous avez un seul emplacement dans votre code View qui gère la réponse aux notifications de modification du modèle, et un seul endroit pour vous abonner / vous désabonner, ce qui réduit la probabilité d'une fuite de mémoire de référence suspendue.
  3. Il y a beaucoup moins de frais de codage lorsque vous souhaitez ajouter ou supprimer un événement de l'éditeur d'événement. Dans .NET, il existe un mécanisme intégré de publication / abonnement appelé, eventsmais en Java, vous devez créer des classes ou des objets et écrire du code d'abonnement / de désabonnement pour chaque événement.
  4. Le plus gros inconvénient de cette approche est que le code d'abonnement peut ne pas être synchronisé avec le code de publication. Cependant, c'est également un avantage dans certains environnements de développement. Si un nouvel événement est développé dans le modèle et que la vue n'est pas encore mise à jour pour s'y abonner, la vue fonctionnera probablement. De même, si un événement est supprimé, vous avez du code mort, mais il pourrait toujours être compilé et exécuté.
  5. Dans .NET au moins, la réflexion est utilisée très efficacement ici pour appeler automatiquement les getters et les setters en fonction de la chaîne qui a été transmise à l'abonné.

Donc, en bref (et pour répondre à votre question), cela peut être fait, mais l'approche de votre professeur est en faute pour ne pas autoriser au moins une dépendance à sens unique entre la vue et le modèle. Ce n'est tout simplement pas pratique, trop académique et ce n'est pas un bon exemple de la façon d'utiliser les outils à portée de main.


¹ Effectuez une recherche sur Google pour INotifyPropertyChangedtrouver un million de façons de trouver des gens pour éviter d'utiliser la chaîne magique lors de sa mise en œuvre.

Kevin McCormick
la source
Il semble que vous ayez décrit SOAP ici. : D… (mais oui, je comprends votre point et vous avez raison)
Konrad Rudolph
11

enums (ou public static final Strings) doit être utilisé à la place des cordes magiques.

Votre professeur ne comprend pas OO . Par exemple, avoir une classe de véhicule concrète?
Et pour que cette classe comprenne la marque d'un vélo?

Le véhicule doit être abstrait et il doit y avoir une sous-classe concrète appelée Vélo. Je jouerais selon ses règles pour obtenir une bonne note, puis j'oublierais son enseignement.

Farmor
la source
3
Sauf si le but de cet exercice est d'améliorer le code en utilisant de meilleurs principes OO. Dans ce cas, refactorisez.
joshin4colours
2
@ joshin4colours Si StackExchange le notait, vous auriez raison; mais comme le correcteur est la même personne qui a eu l'idée qu'il nous donnerait tous de mauvaises notes parce qu'il sait que sa mise en œuvre était meilleure.
Dan Neely
7

Je pense que vous avez un bon point, les cordes magiques sont mauvaises. Quelqu'un de mon équipe a dû déboguer un problème causé par des cordes magiques il y a quelques semaines (mais c'est dans leur propre code qu'ils ont écrit, alors j'ai gardé la bouche fermée). Et c'est arrivé ... dans l'industrie !!

L'avantage de son approche est qu'il pourrait être plus rapide de coder, en particulier pour les petits projets de démonstration. Les inconvénients sont qu'elle est sujette à des fautes de frappe et plus la chaîne est utilisée, plus le code typo a de chances de gâcher les choses. Et si jamais vous voulez changer une chaîne magique, la refactorisation des chaînes est plus difficile que les variables de refactorisation, car les outils de refactorisation peuvent également toucher des chaînes qui contiennent la chaîne magique (en tant que sous-chaîne) mais qui ne sont pas elles-mêmes la chaîne magique et ne doivent pas être touchées. En outre, vous devrez peut-être conserver un dictionnaire ou un index de chaînes magiques pour que les nouveaux développeurs ne commencent pas à inventer de nouvelles chaînes magiques dans le même but et ne perdent pas de temps à rechercher des chaînes magiques existantes.

Dans ce contexte, il semble qu'un Enum puisse être meilleur que des chaînes magiques ou même des constantes de chaînes globales. De plus, les IDE vous fourniront souvent une sorte d'assistance de code (auto-complétion, refactorisation, recherche de références, etc.) lorsque vous utilisez des chaînes constantes ou des énumérations. Un IDE n'apportera pas beaucoup d'aide si vous utilisez des cordes magiques.

FrustratedWithFormsDesigner
la source
Je suis d'accord que les énumérations sont meilleures que les littéraux de chaîne. Mais même alors, est-il vraiment préférable d'appeler un "stateChangeRequest" avec une clé enum, ou d'appeler directement une méthode sur le modèle? Dans les deux cas, le modèle et la vue sont couplés à cet endroit.
Philip
@Philip: Eh bien, je suppose que pour découpler le modèle et l'afficher à ce stade, vous pourriez avoir besoin d'une sorte de classe de contrôleur pour le faire ...
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner - Oui. Une architecture MVC est la plus découplée
cdeszaq
Bon, si une autre couche peut les dissocier, je ne m'y opposerais pas. Mais cette couche pourrait toujours être typée statiquement et utiliser des méthodes réelles plutôt que des messages de chaîne ou d'énumération pour les connecter.
Philip
6

Utiliser «l'expérience de l'industrie» est un mauvais argument. Votre professeur doit trouver un meilleur argument que cela :-).

Comme d'autres l'ont déclaré, l'utilisation de cordes magiques est certainement mal vue, l'utilisation d'énumérations est considérée comme beaucoup plus sûre. Une énumération a un sens et une portée , pas les cordes magiques. Hormis cela, la manière dont votre professeur dissocie les préoccupations est considérée comme une technique plus ancienne et plus fragile qu'aujourd'hui.

Je vois que @backtodos a couvert cela pendant que je répondais à cela, alors j'ajouterai simplement que selon Inversion of Control (dont Dependency Injection est un formulaire, vous allez parcourir le framework Spring dans l'industrie avant longtemps. ..) est considéré comme un moyen plus moderne de gérer ce type de découplage.

Martijn Verburg
la source
2
Tout à fait d'accord, le milieu universitaire devrait avoir des exigences bien plus élevées que l'industrie. Le milieu universitaire == meilleur moyen théorique; L'industrie == meilleur moyen pratique
Farmor
3
Malheureusement, certains de ceux qui enseignent ce genre de choses dans le monde universitaire le font parce qu'ils ne peuvent pas le couper dans l'industrie. Du côté positif, certains des universitaires sont brillants et ne devraient pas être cachés dans un siège social.
FrustratedWithFormsDesigner
4

Soyons assez clairs; il y a inévitablement un couplage là-dedans. Le fait qu'il existe un tel sujet souscriptible est un point de couplage, tout comme la nature du reste du message (comme la «chaîne unique»). Compte tenu de cela, la question devient alors de savoir si le couplage est plus important lorsqu'il est effectué via des chaînes ou des types. La réponse à cela dépend si vous êtes préoccupé par le couplage distribué dans le temps ou dans l'espace: si les messages vont être conservés dans un fichier avant d'être lus plus tard, ou s'ils seront envoyés à un autre processus (surtout si ce n'est pas écrit dans la même langue), l'utilisation de chaînes peut rendre les choses beaucoup plus simples. L'inconvénient est qu'il n'y a pas de vérification de type; les erreurs ne seront pas détectées avant l'exécution (et parfois même pas alors).

Une stratégie d'atténuation / de compromis pour Java peut consister à utiliser une interface typée, mais où cette interface typée se trouve dans un package séparé. Il doit être composé uniquement de Java interfaceet de types de valeurs de base (par exemple, énumérations, exceptions). Il ne devrait y avoir aucune implémentation d'interfaces dans ce package. Ensuite, tout le monde implémente contre cela et, si la transmission des messages de manière complexe est nécessaire, une implémentation particulière d'un délégué Java peut utiliser des chaînes magiques si nécessaire. Il facilite également la migration du code vers un environnement plus professionnel tel que JEE ou OSGi, et facilite grandement les petites choses comme les tests…

Associés Donal
la source
4

Ce que votre professeur propose, c'est l'utilisation de "cordes magiques". Bien qu'il "couple librement" les classes entre elles, permettant des changements plus faciles, c'est un anti-modèle; les chaînes doivent très, TRÈS rarement être utilisées pour contenir ou contrôler des instructions de code, et lorsque cela doit absolument se produire, la valeur des chaînes que vous utilisez pour contrôler la logique doit être définie de manière centrale et constante de sorte qu'il y ait UNE autorité sur la valeur attendue de toute chaîne particulière.

La raison en est très simple; les chaînes ne sont pas vérifiées par le compilateur pour la syntaxe ou la cohérence avec d'autres valeurs (au mieux, elles sont vérifiées pour la forme appropriée concernant les caractères d'échappement et d'autres formats spécifiques à la langue dans la chaîne). Vous pouvez mettre tout ce que vous voulez dans une chaîne. En tant que tel, vous pouvez obtenir un algorithme / programme désespérément cassé à compiler, et ce n'est que lorsqu'il s'exécute qu'une erreur se produit. Considérez le code suivant (C #):

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

... tout ce code se compile, essayez d'abord, mais l'erreur est évidente avec un deuxième regard. Il existe de nombreux exemples de ce type de programmation, et ils sont tous sujets à ce type d'erreur, MÊME SI vous définissez des chaînes comme constantes (car les constantes dans .NET sont écrites dans le manifeste de chaque bibliothèque dans laquelle elles sont utilisées, qui doivent puis tous être recompilés lorsque la valeur définie est modifiée afin que la valeur change "globalement"). Comme l'erreur n'est pas détectée au moment de la compilation, elle doit être détectée lors des tests au moment de l'exécution, et cela n'est garanti qu'avec une couverture de code à 100% dans les tests unitaires avec un exercice de code adéquat, qui ne se trouve généralement que dans la sécurité absolue la plus absolue. , systèmes en temps réel.

KeithS
la source
2

En fait, ces classes sont encore étroitement couplées. Sauf que maintenant ils sont étroitement couplés de telle manière que le compilateur ne peut pas vous dire quand les choses sont cassées!

Si quelqu'un change "BicycleMake" en "BicyclesMake", personne ne découvrira que les choses sont cassées jusqu'à l'exécution.

Les erreurs d'exécution sont beaucoup plus coûteuses à corriger que les erreurs de compilation - c'est juste toutes sortes de maux.

17 sur 26
la source