Pregunta

Estoy buscando un código que se comporte un poco como un singleton pero no lo es (porque los singleton son malos :) Lo que estoy buscando debe cumplir con estos objetivos:

  1. Hilo seguro
  2. Simple (Comprender y usar, es decir, pocas líneas de código. Las llamadas a la biblioteca están bien)
  3. Rápido
  4. No es un singleton; para las pruebas, debe ser posible sobrescribir el valor (y restablecerlo después de la prueba).
  5. Local (toda la información necesaria debe estar en un solo lugar)
  6. Perezoso (se ejecuta solo cuando el valor es realmente necesario).
  7. Ejecutar una vez (el código en RHS debe ejecutarse una vez y solo una vez)

Código de ejemplo:

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+"!");
}

Tenga en cuenta que los campos son no estáticos; solo el RHS (lado derecho) de la expresión es ... bueno '' estático '' hasta cierto grado. Una prueba debería poder inyectar nuevos valores para i y greet temporalmente.

También tenga en cuenta que este código describe cómo tengo la intención de utilizar este nuevo código. Siéntase libre de reemplazar runOnce () con cualquier cosa o moverlo a otro lugar (el constructor, tal vez, o un método init () o un getter). Pero cuanto menos LOC, mejor.

Alguna información de fondo:

No estoy buscando Spring, estoy buscando un código que pueda usarse para el caso más común: es necesario implementar una interfaz y nunca habrá una segunda implementación, excepto para las pruebas donde quieres pasar objetos simulados. Además, Spring falla # 2, # 3 y # 5: debe aprender el lenguaje de configuración, debe configurar el contexto de la aplicación en alguna parte, necesita un analizador XML y no es local (la información se extiende por todo el lugar).

Un objeto de configuración global o fábrica no cumple con la factura debido a # 5.

static final está fuera debido a # 4 (no puede cambiar final). static huele debido a problemas con el cargador de clases, pero probablemente lo necesitará dentro de runOnce () . Prefiero poder evitarlo en el LHS de la expresión.

Una posible solución podría ser usar ehcache con una configuración predeterminada que devolvería el mismo objeto. Como puedo poner cosas en el caché, esto también permitiría anular el valor en cualquier momento. Pero tal vez haya una solución más compacta / simple que ehcache (que nuevamente necesita un archivo de configuración XML, etc.).

[EDITAR] Me pregunto por qué tanta gente rechaza esto. Es una pregunta válida y el caso de uso es bastante común (al menos en mi código). Entonces, si no entiende la pregunta (o la razón detrás de ella) o si no tiene una respuesta o no le importa, ¿por qué rechazar? : /

[EDIT2] Si observa el contexto de la aplicación Spring, encontrará que más del 99% de todos los beans tienen una sola implementación. Podrías tener más, pero en la práctica, simplemente no. Entonces, en lugar de separar la interfaz, la implementación y la configuración, estoy mirando algo que solo tiene una implementación (en el caso más simple), un método current () y una o dos líneas de código inteligente para inicializar el resultado para current () una vez (cuando se llama por primera vez) pero al mismo tiempo permite anular el resultado (seguro para subprocesos, si es posible). Piense en ello como un atómico " if (o == null) o = new O (); volver o '' donde puedes anular el o. Tal vez una clase AtomicRunOnceReference es la solución.

En este momento, siento que lo que todos tenemos y usamos diariamente no es lo óptimo, que existe una solución desconcertante que nos hará darnos una palmada y decir "eso es todo". Tal como lo sentimos cuando llegó la primavera hace unos años y nos dimos cuenta de dónde venían todos nuestros problemas únicos y cómo resolverlos.

¿Fue útil?

Solución 2

Aquí hay una solución que cumple todos mis requisitos:

/** 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;
    }
}

Otros consejos

El mejor modismo para el código de inicialización seguro (thread) es la clase interna perezosa. La versión clásica es

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

    static {
      // create the SINGLETON
    }
  }

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

porque es seguro para subprocesos, carga perezosa y (en mi humilde opinión) elegante.

Ahora desea que se pueda probar y reemplazar. Es difícil aconsejar sin saber exactamente qué es, pero la solución más obvia sería usar la inyección de dependencia, especialmente si está utilizando Spring y tiene un contexto de aplicación de todos modos.

De esa manera, el comportamiento de su '' singleton '' está representado por una interfaz y simplemente inyecta uno de esos en sus clases relevantes (o en una fábrica para producir uno) y luego, por supuesto, puede reemplazarlo por lo que quiera fines de prueba.

Puede usar técnicas de IoC incluso si no usa un marco de IoC (Spring / Guice / ...). Y es, en mi opinión, la única forma limpia de evitar un Singleton.

Creo que una solución que podría usar es proporcionar un método protegido que se pueda anular en la prueba (una solución que he usado antes para probar el código heredado).

Entonces algo como:

private SomeObject object;

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

Luego, en tu clase de prueba, puedes hacer:

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

Tengo que decir que no estoy demasiado interesado en este patrón, ya que expone el campo protegido donde realmente no lo quieres, pero es útil para sacarte de algunas situaciones en las que la inyección no es una opción

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top