42 votes

Comment comprendre la situation dans une application couplée en vrac?

Nous avons été le développement du code en utilisant le couplage et l'injection de dépendance.

Beaucoup de "service" des classes de style dispose d'un constructeur et une méthode qui implémente une interface. Chaque classe est très facile à comprendre dans l'isolement.

Toutefois, en raison de la nature de l'assemblage, à la recherche à une classe vous dit rien sur les classes autour d'elle ou si elle s'inscrit dans la grande image.

Il n'est pas facile de sauter à des collaborateurs à l'aide d'Eclipse parce que vous avez à passer par les interfaces. Si l'interface est - Runnable, qui n'est d'aucune aide dans la recherche de la classe à laquelle est effectivement branché. Vraiment, il est nécessaire de revenir à la DI la définition de conteneur et d'essayer de comprendre les choses à partir de là.

Voici une ligne de code à partir d'une dépendance injecté classe de service:-

  // myExpiryCutoffDateService was injected, 
  Date cutoff = myExpiryCutoffDateService.get();

Couplage ici est aussi lâche que possible. L'expiration de la date de la mise en œuvre littéralement en aucune manière.

Voici à quoi il pourrait ressembler dans un plus couplé application.

  ExpiryDateService = new ExpiryDateService();
  Date cutoff = getCutoffDate( databaseConnection, paymentInstrument );

À partir de la étroitement associée version, je peux en déduire que la date limite est en quelque sorte déterminée à partir de l'instrument de paiement à l'aide d'une connexion de base de données.

Je suis la recherche de code du premier style plus difficile à comprendre que le code de la deuxième de style.

Vous pourriez dire que lors de la lecture de cette classe, je n'ai pas besoin de savoir comment la date limite est compris. C'est vrai, mais si je suis rétrécissement sur un bogue ou de travail, d'où une amélioration des besoins de logement, c'est de l'information utile à savoir.

Est ce que quelqu'un d'autre rencontre ce problème? Quelles solutions avez-vous? Est-ce juste quelque chose à régler pour? Existe-il des outils pour permettre la visualisation de la façon dont les classes sont câblés ensemble? Dois-je rendre les classes plus grandes ou plus couplé?

(Avons délibérément laissé de côté cette question conteneur-agnostique que je suis intéressé par les réponses à tout).

34voto

Mark Seemann Points 102767

Alors que je ne sais pas comment répondre à cette question en un seul paragraphe, j'ai tenté de répondre dans un post de blog à la place: http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx

Pour résumer, je trouve que les points les plus importants sont:

  • La compréhension d'un couplage lâche base de code nécessite une mentalité différente. Alors qu'il est plus difficile de "faire le saut" à des collaborateurs, il devrait également être plus ou moins pertinent.
  • Le couplage est tout au sujet de la compréhension d'une partie, sans la compréhension de l'ensemble. Il est rarement nécessaire de comprendre tout cela en même temps.
  • Lors de la mise à zéro dedans sur un bug, vous devez vous appuyer sur les traces de pile plutôt que de la structure statique du code, en vue d'apprendre à propos de collaborateurs.
  • Il est de la responsabilité des développeurs d' écrire du code pour s'assurer qu'il est facile de comprendre - ce n'est pas de la responsabilité du développeur de la lecture du code.

12voto

Igby Largeman Points 8671

Certains outils sont au courant de la DI de cadres et de savoir comment résoudre les dépendances, vous permettant de naviguer dans votre code de façon naturelle. Mais quand ce n'est pas disponible, vous avez juste à utiliser quelles que soient les caractéristiques de votre IDE du mieux que vous pouvez.

J'utilise Visual Studio et un cadre, de sorte que le problème que vous décrivez est ma vie. Dans Visual Studio, MAJ+F12 est mon ami. Il montre toutes les références pour le symbole sous le curseur. Après un certain temps, vous vous habituez à l'nécessairement non-linéaire de la navigation par le biais de votre code, et il devient une seconde nature pour penser en termes de "classe qui implémente cette interface" et "où est l'injection et/ou de la configuration du site afin que je puisse voir quelle classe est utilisé pour satisfaire à cette interface de dépendance".

Il y a aussi les extensions disponibles pour VS qui fournissent des améliorations de l'INTERFACE utilisateur pour aider à cela, comme la Productivité des Outils électriques. Par exemple, vous pouvez passer la souris sur une interface, une info boîte de dialogue s'affiche, et vous pouvez cliquer sur "mise en œuvre Par" pour voir toutes les classes de votre solution de mise en œuvre de cette interface. Vous pouvez double-cliquer pour sauter à la définition de l'une de ces classes. (J'ai toujours l'habitude de simplement utiliser MAJ+F12 de toute façon).

8voto

Mark Seemann Points 102767

Je viens d'avoir une discussion interne à ce sujet, et a terminé la rédaction de cet article, qui je pense est trop bon de ne pas partager. Je suis le copier ici (presque) non édité, mais même si il fait partie d'un grand débat interne, je pense que la plupart d'autonome.

La discussion porte sur l'introduction d'une interface personnalisée appelé IPurchaseReceiptService, et si oui ou non il doit être remplacé par l'utilisation d' IObserver<T>.


Eh bien, je ne peux pas dire que j'ai des points de données à propos de n'importe - c'est juste quelques théories que je poursuis... Cependant, ma théorie à propos de surcharge cognitive au moment où va quelque chose comme ceci: considérez votre spécial IPurchaseReceiptService:

public interface IPurchaseReceiptService
{
    void SendReceipt(string transactionId, string userGuid);
}

Si nous continuons comme en-Tête de l'Interface il l'est actuellement, il n'y a que le single SendReceipt méthode. C'est cool.

Ce n'est pas si cool, c'est que vous avez eu à trouver un nom pour l'interface, et de l'autre nom de la méthode. Il y a un peu de chevauchement entre les deux: le mot de la Réception apparaît deux fois. IME arrive parfois que le recouvrement peut être encore plus prononcé.

En outre, le nom de l'interface est - IPurchaseReceiptService, ce qui n'est pas particulièrement utile. Le Service suffixe est essentiellement le nouveau Manager, et de l'est, de l'OMI, une conception de l'odorat.

En outre, non seulement avez-vous du nom de l'interface et de la méthode, mais vous avez aussi le nom de la variable lorsque vous l'utilisez:

public EvoNotifyController(
    ICreditCardService creditCardService,
    IPurchaseReceiptService purchaseReceiptService,
    EvoCipher cipher
)

À ce stade, vous avez dit en substance la même chose trois fois. C'est, selon ma théorie, cognitifs généraux, et de l'odeur que le design pourrait et devrait être plus simple.

Maintenant, contraste avec l'utilisation d'une interface connue comme IObserver<T>:

public EvoNotifyController(
    ICreditCardService creditCardService,
    IObserver<TransactionInfo> purchaseReceiptService,
    EvoCipher cipher
)

Cela vous permet de se débarrasser de la bureaucratie et de réduire la conception de l'au cœur de la question. Vous avez encore l'intention de révéler de nommage - vous qu'à bouger la conception à partir d'un Nom de Type de Rôle Allusion à un Nom d'Argument Rôle de l'Indice.


Quand il s'agit de la discussion sur "l'irréalité", je suis pas d'illusion que l'utilisation d' IObserver<T> magie faire de ce problème, mais j'ai une autre théorie sur le sujet.

Ma théorie est que la raison pour laquelle de nombreux programmeurs trouver programmation d'interfaces si difficiles, c'est précisément parce qu'elles sont utilisées pour Visual Studio, Allez à la définition de la fonction (d'ailleurs, c'est encore un autre exemple de la façon dont l'outillage pourrit l'esprit). Ces programmeurs sont perpétuellement dans un état d'esprit où ils ont besoin de savoir ce qui est "de l'autre côté de l'interface". Pourquoi est-ce? Serait-ce parce que l'abstraction est-elle mauvaise?

Ce lien avec le RAP, parce que si vous confirmez les programmeurs sont d'avis qu'il y a un seul, notamment la mise en œuvre derrière chaque interface, il n'est pas étonnant qu'ils pensent que les interfaces ne sont que de la manière.

Toutefois, si vous appliquez le RAP, j'espère que peu à peu, les programmeurs vont apprendre que derrière une interface particulière, il peut être tout de la mise en œuvre de cette interface, et leur code client doit être capable de gérer toute la mise en œuvre de cette interface sans changer l'exactitude du système. Si cette théorie est, nous avons introduit le Principe de Substitution de Liskov dans une base de code sans effrayer n'importe qui avec haut-front des concepts qu'ils ne comprennent pas :)

7voto

Cratylus Points 21838

Toutefois, en raison de la nature de l'assemblage, à la recherche à une classe vous dit rien sur les classes autour d'elle ou si elle s'inscrit dans le agrandir l'image.

Ce n'est pas précis.Pour chaque classe, vous savez exactement ce genre d'objets de la classe dépend, pour être en mesure de fournir à ses fonctionnalités au moment de l'exécution.
Vous la connaissez, puisque vous savez que ce que les objets doivent être injectés.

Ce que vous ne savez pas, c'est le réel de la classe de béton qui sera injecté au moment de l'exécution qui va implémenter l'interface ou classe de base que vous connaissez votre classe(s) dépendent.

Donc, si vous voulez voir ce qu'est la classe réelle de l'injection, vous avez juste à regarder le fichier de configuration de la catégorie pour voir les classes concrètes qui sont injectés.

Vous pouvez également utiliser les installations fournies par votre IDE.
Puisque vous vous référez à Eclipse puis le Printemps a un plugin pour ça, et a également un onglet visual afficher les haricots que vous configurez. Avez-vous vérifier? N'est-ce pas ce que vous recherchez?

Consultez également la même discussion dans le Forum du Printemps

Mise à JOUR:
La lecture de votre question, je ne pense pas que c'est une vraie question.
Je veux dire de la manière suivante.
Comme toutes choses, loose coupling n'est pas une panacée, et a ses propres inconvénients en soi.
La plupart ont tendance à se concentrer sur les avantages, mais comme toute solution, il a ses inconvénients.

Ce que vous faites dans votre question est de décrire l'un de ses principaux inconvénients est qu'il n'est en effet pas facile de voir la grande image, puisque vous avez tout configurable et branché à rien.
Il y a d'autres inconvénients que l'on pourrait plainte, par exemple, que c'est plus lent que serré couplé applications et être toujours vrai.

En tout cas, ré-itération, ce que vous décrivez dans votre question n'est pas un problème, vous entrez sur et peut trouver une solution standard (ou tout de cette manière).

Il est l'un des inconvénients du couplage lâche et vous devez décider si ce coût est supérieur à ce que vous avez réellement obtenir par elle, comme dans toute la conception de décision de l'échange.

C'est comme de demander:
Hey, je suis l'aide de ce modèle nommé Singleton. Il fonctionne très bien mais je ne peux pas créer de nouveaux objets!Comment puis-je obtenir autour de ce problème les gars????
Eh bien, vous ne pouvez pas; mais si vous en avez besoin, peut-être singleton n'est pas pour vous....

5voto

Steven Points 56939

Une chose qui m'a aidé est de placer de multiples étroitement liés classes dans le même fichier. Je sais que cela va à l'encontre de l'avis général (de 1 classe par fichier) et je suis généralement d'accord avec cela, mais dans mon architecture de l'application, il fonctionne très bien. Ci-dessous je vais essayer d'expliquer dans quel cas c'est.

L'architecture de ma couche de gestion est conçu autour de la notion de business commandes. Classes de commandes (simple DTO avec seulement des données et pas de comportement) sont définies et pour chaque commande, il est un "gestionnaire de commande" qui contient la logique métier pour exécuter cette commande. Chaque gestionnaire de commande met en œuvre le générique ICommandHandler<TCommand> interface, où TCommand est à l'entreprise de la commande.

Les consommateurs prennent une dépendance sur l' ICommandHandler<TCommand> et de créer une nouvelle commande d'instances et d'utiliser l'injection de gestionnaire pour exécuter ces commandes. Cela ressemble à ceci:

public class Consumer
{
    private ICommandHandler<CustomerMovedCommand> handler;

    public Consumer(ICommandHandler<CustomerMovedCommand> h)
    {
        this.handler = h;
    }

    public void MoveCustomer(int customerId, Address address)
    {
        var command = new CustomerMovedCommand();

        command.CustomerId = customerId;
        command.NewAddress = address;

        this.handler.Handle(command);
    }
}

Aujourd'hui, les consommateurs ne dépendent spécifique ICommandHandler<TCommand> et n'ont aucune notion de la mise en œuvre effective (comme il faut). Cependant, bien que l' Consumer devrait ne sais rien à propos de la mise en œuvre, au cours du développement I (en tant que développeur) je suis très intéressé par la logique métier qui est exécuté, tout simplement parce que le développement est fait en tranches verticales, ce qui signifie que je suis le plus souvent en travaillant sur les deux l'INTERFACE utilisateur et la logique métier d'une simple fonctionnalité. Ce qui signifie que je suis souvent le passage entre la logique métier et la logique de l'INTERFACE utilisateur.

Donc, ce que j'ai fait a été de mettre la commande (dans cet exemple, l' CustomerMovedCommand et la mise en œuvre de l' ICommandHandler<CustomerMovedCommand>) dans le même fichier, avec la première commande. Parce que la commande elle-même est en béton (depuis sa un DTO il n'y a pas de raison de leur abstraction) saut à la classe est facile (F12 dans Visual Studio). En plaçant le gestionnaire à côté de la commande, le saut à la commande signifie aussi, le saut à la logique d'entreprise.

Bien sûr, cela ne fonctionne que quand il est correct pour la commande et le gestionnaire de vivre dans la même assemblée. Lors de vos commandes doivent être déployés séparément (par exemple lorsque les réutiliser dans un contexte client/serveur scénario), cela ne fonctionnera pas.

Bien sûr, ce n'est qu'à 45% de ma couche de gestion. Un autre gros toutefois, la paix (45%) sont les requêtes et ils sont conçus de la même manière, à l'aide d'une requête de classe et un gestionnaire de requêtes. Ces deux classes sont également placés dans le même fichier qui-encore une fois, me permet d'accéder rapidement à la logique d'entreprise.

Parce que les commandes et les requêtes sont environ 90% de ma couche, je peux, dans la plupart des cas, déplacer très rapidement d'une couche présentation couche de gestion et même de naviguer facilement à l'intérieur de la couche de gestion.

Je dois dire que ce sont les deux seuls cas que je place plusieurs classes dans le même fichier, mais rend la navigation beaucoup plus facile.

Si vous voulez en savoir plus sur la façon dont j'ai conçu ce, j'ai écrit deux articles à ce sujet:

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X