réutilisation de code dans Patterns.class vs modèle de stratégie et dependency injection

Status: réponses de Fendy et de Glen Best sont également acceptables et honorées par moi, mais comme il est possible d’accepter une récompense et d’obtenir une prime, je choisis la réponse de Fendy.

Scenario:

Si du code doit être réutilisé plusieurs fois dans de nombreuses classes (rarement avec des modifications mineures des parameters, ce qui est évident) et des threads simultanés, quelle approche adopter?

Le code qui doit être réutilisé peut être une chose saine (en prenant en compte le contexte statique et non statique et les techniques de création de méthodes). Ce peut être un algorithme, une méthode DB qui connecte, exploite, ferme. N’importe quoi.

  1. Faites des classes comme MyMethods.class et mettez toutes ces méthodes dedans .

    1.a. Rendre les méthodes static et appeler (par toutes les classes et tous les threads simultanés) directement en tant que MyMethods.someMethod();

    1.b. Rendre les méthodes non-static et au moment de les appeler, instantiate la classe entière par MyMethods mm = MyMethods(); mm.someMethod(); MyMethods mm = MyMethods(); mm.someMethod();

  2. Utilisez le modèle de stratégie indiqué à l’ adresse https://en.wikipedia.org/wiki/Strategy_pattern (code joint ici avec).

  3. Utilisez l’ dependency injection indiquée à l’ adresse https://en.wikipedia.org/wiki/Dependency_injection#Java.

Problems:

  1. Certaines personnes diraient que le test unitaire http://en.wikipedia.org/wiki/Unit_testing ne sera pas possible avec cette approche, ce qui rendra plus difficile l’échange de cette dernière. si vous voulez tester votre classe et utiliser une version simulée de la dépendance

    1.a. Y aura-t-il des problèmes avec des appels simultanés ou des classes multiples? spécialement dans JDBC static methods pour un exemple?

    1.b. Je pense que cela ferait trop de mémoire car une classe entière serait instanticated plusieurs fois pour appeler une ou deux méthodes.

  2. C’est bien au-dessus de ma tête, expliquez cela et / ou les avantages / inconvénients

  3. Je ne veux pas utiliser un cadre dans le contexte de cette question. C’est clair, expliquez-le et, le cas échéant, avec ses avantages / inconvénients.

  4. En attente de toute autre stratégie ou recommandation, le cas échéant.

Request: Veuillez ne répondre que si vous êtes expérimenté et si vous connaissez bien les implications et pouvez, avec votre réponse, m’aider et aider la communauté dans son ensemble!

Code:

 /** The classes that implement a concrete strategy should implement this. * The Context class uses this to call the concrete strategy. */ interface Strategy { int execute(int a, int b); } /** Implements the algorithm using the strategy interface */ class Add implements Strategy { public int execute(int a, int b) { System.out.println("Called Add's execute()"); return a + b; // Do an addition with a and b } } class Subtract implements Strategy { public int execute(int a, int b) { System.out.println("Called Subtract's execute()"); return a - b; // Do a subtraction with a and b } } class Multiply implements Strategy { public int execute(int a, int b) { System.out.println("Called Multiply's execute()"); return a * b; // Do a multiplication with a and b } } // Configured with a ConcreteStrategy object and maintains // a reference to a Strategy object class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public int executeStrategy(int a, int b) { return this.strategy.execute(a, b); } } /** Tests the pattern */ class StrategyExample { public static void main(Ssortingng[] args) { Context context; // Three contexts following different strategies context = new Context(new Add()); int resultA = context.executeStrategy(3,4); context = new Context(new Subtract()); int resultB = context.executeStrategy(3,4); context = new Context(new Multiply()); int resultC = context.executeStrategy(3,4); System.out.println("Result A : " + resultA ); System.out.println("Result B : " + resultB ); System.out.println("Result C : " + resultC ); } } 

Votre question a en fait deux sens.

qui doit être réutilisé plusieurs fois dans de nombreuses classes

Il peut s’agir d’un contexte de modèle de conception (composant réutilisable) ou de coût en mémoire (instanciation de classe). Parler de deux outlook différentes:

Coût en mémoire (j’avais peu d’expérience à ce sujet, mais laissez-moi partager mon expérience)

Cette section ne couvre en réalité que 2 types d’instanciation.

Le premier est statique (ou instanciation DI dans la racine de la composition)

  • Instanciation rapide, signifie que toutes les classes seront instanciées au démarrage de l’application
  • Instanciation unique

Non statique

  • Instanciation paresseuse, signifie que la classe ne sera instanciée qu’en cas de besoin
  • Instanciation unique à chaque utilisation

En bref, le coût statique sera élevé si la classe est nombreuse et non statique si la demande est élevée (boucle interne, par exemple). Mais cela ne devrait pas alourdir votre application. La plupart des opérations dans java / csharp sont des objects créés.

Réutilisation de classe

1 – méga code monolithique (une classe de dieu capable de presque tout faire)

Avantages:

  1. Il est facile de rechercher du code (cela dépend quand même), vous savez que toutes les logiques sont là, il vous suffit de regarder cette grande classe
  2. S’il est statique, vous pouvez simplement l’appeler n’importe où sans vous soucier de l’instanciation.

Désavantages:

  1. Toute modification d’une méthode crée un risque d’erreur à d’autres endroits.
  2. Enfreint SRP, signifie que cette classe peut être changée pour différentes raisons, pas une seule
  3. Surtout dans la gestion des versions, il est plus difficile de fusionner si la modification a lieu dans des twigs séparées, ce qui entraîne un effort de synchronisation du code.

Modèle 1a / classe statique / singleton

Avantages:

  1. Facile à utiliser
  2. Peut être utilisé n’importe où (référencez et appelez, et faites)
  3. Pas besoin d’instancier l’object

Désavantages:

  1. Difficile de faire des tests unitaires (il est difficile de se moquer de vous, et à ce dernier moment, vous constaterez qu’il faut du temps pour préparer l’environnement de test. En particulier avec des données
  2. Si stateful (a l’état), il est difficile de déterminer l’état actuel pendant le débogage. De plus, il est difficile de déterminer quelle fonction change l’état, elle peut être changée de partout
  3. Ont tendance à avoir beaucoup de parameters (peut-être autour de 5-11)

Quelques points sur la classe statique: voir cette question

2 modèle de stratégie

En fait, cela a la même conception avec 3 ou la composition over inheritance .

3 dependency injection

Avantages:

  • Facile à simuler et test unitaire
  • Must be stateless . Il est plus facile de déboguer et de tester l’unité si la classe est sans état
  • Support à refactoriser

Le désavantage:

  • Difficile à déboguer pour ceux qui ne sont pas familiers avec les interfaces (chaque fois que vous redirigez la méthode, celle-ci passe à l’interface)
  • Crée une superposition qui résultera en un mappage

Etat / Apasortingde

Je pense que les États jouent des règles importantes dans la conception de votre application. Les développeurs essaient généralement d’éviter d’avoir des états dans le code de la logique métier, tels que:

 // get data if(request.IsDraft){ // step 1 // step 2 } else{ // step 1 // step 3 } 

Les développeurs ont tendance à mettre la logique dans une autre classe stateless , ou du moins des méthodes telles que:

 // get data if(request.IsDraft){ draftRequestHandler.Modify(request); } else{ publishedRequestHandler.Modify(request); } 

Il offrira une meilleure lisibilité et facilitera les modifications et les tests unitaires. Il existe également un modèle d’ state pattern or hierarchial state machine pattern conception state pattern or hierarchial state machine pattern , en particulier pour traiter certains cas de ce type.

Principe de responsabilité unique

IMHO, ce principe est le plus avantageux s’il est suivi. Les avantages sont:

  1. Dans le contrôle de version, les modifications sont claires quant à la classe modifiée et aux raisons pour lesquelles
  2. Dans DI, plusieurs classes plus petites peuvent être câblées, ce qui crée une souplesse d’utilisation et de test unitaire
  3. Augmente la modularité, le faible couplage et la grande cohésion

TDD (développement piloté par les tests)

Cette conception ne garantit pas que votre code est exempt de bugs. Contre le coût de temps en phase de conception et l’effort de superposition, il présente les avantages suivants:

  1. Objet facile à simuler pour le test unitaire
  2. Plus facile à refactoriser grâce au test unitaire et à la modularité
  3. Plus facile à maintenir / à étendre

Quelques sources utiles

Service Locator Anti Pattern

Utilisation de Decorator pour des problèmes transversaux

Mes 2 centimes:

L’avantage d’utiliser l’interface (s’applique également à la composition de l’inheritance)

Faire du design descendant / design DI

Dernières pensées

Ces conceptions et stratégies ne sont pas la clé qui déterminera la structure de votre application. C’est toujours l’architecte qui le déterminera. Je préfère suivre certains principes tels que SOLID, KISS et GRASP, plutôt que de décider quelle est la meilleure structure. Il est dit que l’dependency injection suit la plupart de ces principes, mais une trop grande abstraction et une conception de composants incorrecte entraîneront le même résultat avec l’utilisation abusive du modèle de singleton.

1.a. Rendre les méthodes statiques et appeler (par toutes les classes et tous les threads simultanés) directement en tant que MyMethods.someMethod ();

1.b. Rendre les méthodes non statiques et au moment de les appeler, instancier la classe entière par MyMethods mm = MyMethods (); mm.someMethod ();

Le choix entre ces deux dépend de la fonctionnalité de MyMethods.class . Si MyMethods est censé être sans état, c’est une bonne approche pour utiliser static méthodes static . Sinon, si un appel de méthode dépend d’un autre et que MyMethods a des états (c’est-à-dire des champs non finaux), utilisez la deuxième option.

Utilisez le modèle de stratégie indiqué à l’ adresse https://en.wikipedia.org/wiki/Strategy_pattern (code joint ici avec).

Utilisez ce modèle si MyMethods doit être étendu avec différentes classes à des fins différentes et si vous sélectionnez le code à exécuter en fonction de votre contexte. Comme le dit le wiki , si vous ne connaissez pas l’algorithme à utiliser avant l’exécution (cela dépend de certaines conditions), c’est la voie à suivre. Selon vos spécifications de MyMethods vous n’avez pas de tels problèmes.

Utilisez l’dependency injection indiquée à l’ adresse https://en.wikipedia.org/wiki/Dependency_injection#Java.

Même réponse que ci-dessus. La chose avec l’ dependency injection est en inversion de contrôle . Une classe qui utilise MyMethods ne connaît pas la mise en œuvre réelle de MyMethods , mais l’injection de la mise en œuvre réelle est déléguée à une autorité de niveau supérieur. Il abstrait les dépendances externes du contexte dans lequel il va être utilisé. Encore une fois, si MyMethods doit être sans état et constant (il n’est pas prévu de changer, et le comportement des méthodes dans la classe ne dépend pas du contexte dans lequel elles sont utilisées), vous n’avez pas besoin de ces modèles, car cela signifierait simplement une ingénierie excessive.

Je conclurais que vous devriez utiliser un modèle Strategy ou DI si la logique de MyMethods dépend du contexte dans lequel ils sont exécutés. Si ceci est constant (par exemple, la classe Math de Java ne se soucie pas de savoir qui ou dans quel contexte quelqu’un appelle sqrt() , max() ou pow() ), alors les méthodes statiques sont la solution.


Concernant les problèmes:

Les problèmes que vous avez indiqués ne sont pas présents lorsque vous utilisez MyMethods avec static méthodes static . Vous devrez vérifier si vos méthodes renvoient des valeurs correctes pour des arguments particuliers, et c’est tout. Je ne pense pas qu’il y aurait beaucoup plus de difficulté à tester la mise en œuvre réelle de Strategy in Strategy ou la mise en œuvre d’interface injectée via l’ dependency injection . Ce qui pourrait être plus difficile, c’est de tester les classes qui utilisent la stratégie, car il n’est parfois pas facile de recréer le contexte dans lequel une stratégie particulière sera exécutée, car cela dépend souvent de l’entrée de l’utilisateur, mais ce n’est certainement pas impossible. L’dependency injection est, en ce qui me concerne, un excellent moyen de test car vous pouvez séparer l’unité sous test de la dépendance que vous pouvez facilement simuler.

  1. La question principale: la réutilisation du code

    Si du code doit être réutilisé plusieurs fois dans de nombreuses classes (rarement avec des modifications mineures des parameters, ce qui est évident) et des threads simultanés, quelle approche adopter?

    Parce que vous n’envisagez pas de couper-coller, je pense que vous voulez dire:

    … réutilisé plusieurs fois par plusieurs classes …

    Ce que vous demandez n’est rien de spécial ou de spécifique. Il est courant que le code soit réutilisé, soit dans une seule application, soit dans plusieurs applications. La réponse commune: utilisez une conception / programmation orientée object . Mettez le code dans une classe, créez un object en tant qu’instance, appelez l’object …

    1a. Réutilisation via des méthodes statiques:

    Rendre les méthodes statiques et appeler (par toutes les classes et tous les threads simultanés) directement en tant que MyMethods.someMethod ()

    • Si votre classe est sans état (pas de variables d’instance), c’est une excellente approche.
    • Si votre classe a un état de niveau classe (variables d’instance statiques uniquement), mais que les variables sont en lecture seule (immuable), il s’agit d’une bonne approche.
    • Si votre classe a un état de niveau classe (variables d’instance statiques uniquement) et que les variables changent de valeur (modifiable), cette approche peut alors être appropriée. Toutefois, si vous souhaitez que votre classe soit accessible à partir de plusieurs threads, vous devez la rendre thread-safe: synchronisez vos méthodes ou disposez d’un code interne qui synchronise (access de thread mutuellement exclusif) pour toutes les lectures et écritures de données.
    • Si votre code a un état de niveau object (variables d’instance non statiques), cette approche ne fonctionnera pas – impossible d’accéder aux variables d’instance non statiques sans instancier un object.

    1b. Réutilisation via des méthodes non statiques, avec instanciation d’object:

    Rendre les méthodes non statiques et au moment de les appeler, instancier la classe entière par MyMethods mm = MyMethods (); mm.someMethod ();

    • Si votre classe n’a que des variables d’instance statiques, c’est une mauvaise approche, car instancier l’object ne donne rien
    • Si votre classe a des variables d’instance non statiques, c’est la seule approche. Obligatoire pour instancier un object pour accéder aux variables.
    • Si les objects instanciés doivent être utilisés sur plusieurs threads, ils doivent être (par ordre de préférence):
      • stateless (aucune variable d’instance) – vraiment une option pour 1a – pas besoin d’instancier
      • immuable (variables d’instance non statiques en lecture seule)
      • synchronisé sur toutes les données lit et écrit
  2. Utiliser un modèle de stratégie

    Le modèle de stratégie peut être une bonne pratique. Mais cela a peu à voir avec votre question globale. Le modèle de stratégie est utilisé pour une raison spécifique – pour permuter l’implémentation de la logique algorithme / traitement “à la volée” sans impacter l’appelant.

  3. Utiliser l’dependency injection

    L’dependency injection est utilisée pour les raisons suivantes:

    • Fonctionnalité de cache d’usine et d’object: supprime de votre code la responsabilité de la création, de la mise en cache et de la recherche d’object
    • Intermédiation pour le partage d’objects: permet à différentes classes de partager la même instance d’object (stockée dans une scope / un contexte donné), sans que les deux classes ne transmettent directement l’object entre elles.
    • “Contrôle de câblage” entre les instances d’object – Configuration d’associations d’object, et sous CDI, prise en charge des modèles d’intercepteur, de décorateur et d’observateur

    Cela peut être une très bonne pratique, si elle est utilisée correctement. Dans votre cas, cela ne pourrait jamais s’appliquer que dans le cadre de l’option 1b. L’dependency injection concerne l’instanciation et la provision d’objects dans des variables.

Problèmes:

  1. Certaines personnes diraient que le test unitaire ne sera pas possible

    • Les frameworks moqueurs (et les tests unitaires codés à la main) permettent de remplacer les classes par une logique fictive, à tout moment . C’est un scénario très normal. Vous pouvez étendre une classe pour simuler sa logique – si elle n’a pas de méthodes publiques finales. En outre, vous pouvez transférer des déclarations de méthode vers une interface, demander à la classe de la mettre en œuvre, puis de la simuler en implémentant l’interface avec une classe différente.
    • En d’autres termes, ce n’est pas une contrainte / force qui affecte l’une de vos options

    1a. Voir au dessus

    1b. Chargement de la mémoire

    Je pense que cela ferait trop de mémoire car une classe entière serait instanciée plusieurs fois pour appeler une ou deux méthodes.

    Un petit problème. Selon les données dans chaque instance d’object (les variables d’instance), chaque instance d’object peut être aussi petite qu’une douzaine d’octets ou aussi grande que des mégaoctets – mais est généralement inclinée vers le bas (souvent <1 Ko). La consommation de mémoire du code de classe n’est pas répliquée à chaque fois que la classe est instanciée.

    Bien sûr, il est judicieux de minimiser le volume d’objects, en fonction de vos besoins. Ne créez pas d’instance si vous en avez déjà une utilisable. Créez moins d’instances d’object et partagez-les sur votre application, en les transmettant aux méthodes de constructeur et aux méthodes de définition. L’dependency injections est un bon moyen de partager des instances d’object “automatiquement” sans les transmettre aux constructeurs / setters.

  2. Voir au dessus

  3. Voir au dessus