Méthodes d'initialisation paresseuse «exécutée une fois» en Java avec remplacement des tests unitaires

StackOverflow https://stackoverflow.com/questions/442589

Question

Je cherche un morceau de code qui se comporte un peu comme un singleton mais ne le soit pas (car les singleton sont mauvais :) Ce que je recherche doit atteindre ces objectifs:

  1. Fil de sécurité
  2. Simple (Comprenez et utilisez, c'est-à-dire quelques lignes de code. Les appels à la bibliothèque sont OK)
  3. rapide
  4. Pas un singleton; pour les tests, il doit être possible d'écraser la valeur (et de la réinitialiser après le test).
  5. Local (toutes les informations nécessaires doivent figurer au même endroit)
  6. Lazy (exécuté uniquement lorsque la valeur est réellement nécessaire).
  7. Exécuter une fois (le code sur RHS doit être exécuté une fois et une seule fois)

Exemple de code:

private int i = runOnce(5); // Set i to 5
// Create the connection once and cache the result
private Connection db = runOnce(createDBConnection("DB_NAME"));

public void m() {
    String greet = runOnce("World");
    System.out.println("Hello, "+greet+"!");
}

Notez que les champs ne sont pas statiques; seul le RHS (côté droit) de l'expression est ... bien "statique" dans une certaine mesure. Un test doit pouvoir injecter de nouvelles valeurs pour i et saluer temporairement.

Notez également que ce morceau de code explique comment je compte utiliser ce nouveau code. N'hésitez pas à remplacer runOnce () par quelque chose ou à le déplacer (le constructeur, peut-être, une méthode init () ou un getter). Mais moins il y aura de LOC, mieux ce sera.

Quelques informations de base:

Je ne cherche pas Spring, je cherche un morceau de code pouvant être utilisé dans le cas le plus courant: vous devez implémenter une interface et il n'y aura jamais de seconde implémentation, sauf pour les tests où vous voulez passer dans des objets fictifs. De plus, Spring échoue avec les commandes n ° 2, n ° 3 et n ° 5: vous devez apprendre le langage de configuration, vous devez configurer le contexte de l'application quelque part, il nécessite un analyseur XML et ce n'est pas local (les informations sont réparties dans tous les sens).

Un objet ou une usine de configuration globale ne répond pas à la facture à cause de # 5.

statique final est annulé à cause de # 4 (impossible de changer de final). static sent à cause de problèmes de chargeur de classe, mais vous en aurez probablement besoin dans runOnce () . Je préférerais simplement pouvoir l'éviter dans le LHS de l'expression.

Une solution possible pourrait être d’utiliser ehcache avec une configuration par défaut qui renverrait le même objet. Puisque je peux mettre des choses dans le cache, cela permettrait également de remplacer la valeur à tout moment. Mais peut-être existe-t-il une solution plus simple et compacte que ehcache (qui nécessite à nouveau un fichier de configuration XML, etc.).

[EDIT] Je me demande pourquoi tant de gens refusent cela. C'est une question valide et le cas d'utilisation est assez courant (du moins dans mon code). Donc, si vous ne comprenez pas la question (ou la raison derrière celle-ci) ou si vous n'avez pas de réponse ou si vous vous en fichez, pourquoi voter à la baisse? : /

[EDIT2] Si vous examinez le contexte d'application de Spring, vous constaterez que plus de 99% des beans ont une seule implémentation. Vous pourriez en avoir plus, mais dans la pratique, vous n'en avez tout simplement pas. Ainsi, au lieu de séparer l'interface, l'implémentation et la configuration, je cherche quelque chose qui n'a qu'une implémentation (dans le cas le plus simple), une méthode current () et une ou deux lignes de code intelligent pour initialiser le résultat pour current (). une fois (lorsqu’il est appelé pour la première fois), mais simultanément, permet d’ignorer le résultat (thread-safe, si possible). Considérez-le comme un atome "if (o == null) o = new O (); retourner o " où vous pouvez remplacer le o. Peut-être qu'une classe AtomicRunOnceReference est la solution.

Pour le moment, j’ai juste le sentiment que ce que nous avons tous et utilisons quotidiennement n’est pas l’optimum, mais qu’il existe une solution déroutante qui nous fera tous gifler et dire "c’est tout". Comme nous le sentions lorsque le printemps est arrivé il y a quelques années, nous avons réalisé d'où venaient tous nos problèmes de singleton et comment les résoudre.

Était-ce utile?

La solution 2

Voici une solution qui répond à toutes mes exigences:

/** Lazy initialization of a field value based on the (correct)
* double checked locking idiom by Joschua Bloch
*
* <p>See "Effective Java, Second Edition", p. 283
*/
public abstract class LazyInit<T>
{
    private volatile T field;

    /** Return the value.
    *
    *  <p>If the value is still <code>null</code>, the method will block and
    *  invoke <code>computeValue()</code>. Calls from other threads will wait
    *  until the call from the first thread will complete.
    */
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("UG_SYNC_SET_UNSYNC_GET")
    public T get ()
    {
        T result = field;
        if (result == null) // First check (no locking)
        {
            synchronized (this)
            {
                result = field;
                if (result == null) // Second check (with locking)
                {
                    field = result = computeValue ();
                }
            }
        }
        return result;
    }

    protected abstract T computeValue ();

    /** Setter for tests */
    public synchronized void set (T value)
    {
        field = value;
    }

    public boolean hasValue()
    {
        return field != null;
    }
}

Autres conseils

Le meilleur langage pour le code d’initialisation threadsafe (imho) est la classe interne paresseuse. La version classique est

class Outer {
  class Inner {
    private final static SomeInterface SINGLETON;

    static {
      // create the SINGLETON
    }
  }

  public SomeInterface getMyObject() {
    return Inner.SINGLETON;
  }
}

parce que c'est threadsafe, chargement paresseux et (à mon humble avis) élégant.

Maintenant, vous voulez la testabilité et la remplaçabilité. Il est difficile de conseiller sans savoir exactement ce que c'est, mais la solution la plus évidente serait d'utiliser l'injection de dépendance, en particulier si vous utilisez Spring et avez toujours un contexte d'application.

Ainsi, le comportement de votre "singleton" est représenté par une interface. Vous en injectez simplement une dans vos classes appropriées (ou une usine pour en produire une) et vous pouvez bien sûr la remplacer par celle de votre choix. à des fins de test.

Vous pouvez utiliser les techniques IoC même si vous n’utilisez pas de framework IoC (Spring / Guice / ...). Et c’est à mon avis le seul moyen propre d’éviter un Singleton.

Je pense qu'une solution que vous pouvez utiliser est de fournir une méthode protégée qui peut être annulée lors du test (une solution que j'avais déjà utilisée pour tester du code hérité).

Donc quelque chose comme:

private SomeObject object;

protected SomeObject getObject() {
   if (object == null) {
       object = new SomeObject();
   }
   return object;
}

Ensuite, dans votre classe de test, vous pouvez faire:

public void setUp() {
   MyClassUnderTest cut = new MyClassUserTest() {
      @Override
      protected SomeObject getObject() }
         return mockSomeObject;
      }
   };
}

Je dois dire que je ne suis pas très enthousiaste à l'idée de ce modèle car il expose le champ protégé où vous ne le souhaitez peut-être pas vraiment, mais il est utile pour vous sortir de certaines situations où l'injection n'est pas une option

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top