Maneiras para preguiçoso “Executar uma vez” inicialização em Java com substituição de testes de unidade

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

Pergunta

Eu estou procurando um pedaço de código que se comporta um pouco como um singleton, mas não é (porque Singleton são ruins :) O que eu estou procurando deve atender a estes objetivos:

  1. seguro de rosca
  2. Simples (Entenda & uso, ou seja, algumas linhas de código. Chamadas de biblioteca são OK)
  3. rápido
  4. Não um singleton; para testes, deve ser possível para substituir o valor (e redefini-la após o teste).
  5. Local (todas as informações necessárias devem estar em um lugar)
  6. preguiçoso (executar somente quando o valor é realmente necessário).
  7. Executar uma vez (código em RHS deve ser executado uma vez e apenas uma vez)

código Exemplo:

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

Note que os campos são não estática; apenas o RHS (lado direito) da expressão é ... bem "estático" em algum grau. Um teste deve ser capaz de injetar novos valores para i e greet temporariamente.

Observe também que este pedaço de contornos código como eu pretendo usar esse novo código. Sinta-se livre para substituir runonce () com alguma coisa ou movê-lo para algum outro lugar (o construtor, talvez, ou um método init () ou um getter). Mas a menos LOC, melhor.

Algumas informações básicas:

Eu não estou olhando para a Primavera, eu estou procurando um pedaço de código que pode ser usado para o caso mais comum: Você precisa implementar uma interface e não será nunca uma segunda aplicação, exceto para testes em que você quer passar em objetos mock. Além disso, Primavera falha # 2, # 3 e # 5: Você precisa aprender a linguagem de configuração, você deve configurar a algum lugar contexto aplicativo, ele precisa de um parser XML e não é local (informação está espalhada por todo o lugar) <. / p>

Um objeto de configuração global ou de fábrica não atender o projeto de lei por causa de # 5.

static final está fora por causa de # 4 (não pode mudar final). static cheira por causa de questões carregador de classe, mas você provavelmente vai precisar dele dentro runOnce(). Eu simplesmente preferem ser capaz de evitá-lo no LHS da expressão.

Uma possível solução seria usar ehcache com uma configuração padrão que retornaria o mesmo objeto. Desde que eu posso colocar as coisas no cache, o que também permitiria substituir o valor a qualquer momento. Mas talvez haja uma solução mais compacto / simples do que ehcache (que novamente precisa de um arquivo de configuração XML, etc).

[EDIT] Eu estou querendo saber por que tantas pessoas downvote isso. É uma pergunta válida e o caso de uso é bastante comum (pelo menos no meu código). Então, se você não entender a pergunta (ou a razão por trás disso) ou se você tiver nenhuma resposta ou você não se importa, porque downvote? : /

[EDIT2] Se você olhar para o contexto do aplicativo do Spring, você verá que mais de 99% de todos os grãos têm apenas uma única implementação. Você poderia ter mais, mas, na prática, você simplesmente não fazer. Então, ao invés de separar interface, implementação e configuração, eu estou olhando para algo que tem apenas uma implementação (no caso mais simples), um método atual () e uma ou duas linhas de código inteligente para inicializar o resultado para a corrente () uma vez (quando é chamado pela primeira vez), mas ao mesmo tempo permite para substituir o resultado (segmento seguro, se possível). Pense nisso como um atômica "se (o == null) o = new O (); retorno o" onde você pode substituir a o. Talvez uma classe AtomicRunOnceReference é a solução.

Agora, eu apenas sinto que o que todos nós temos e usar diariamente não é o ideal, que não há uma solução desconcertante que fará com que todos nós tapa a cabeça e dizer "é isso". Assim como nós sentimos quando primavera chegou em torno de alguns anos atrás e percebemos que todos os nossos problemas únicas veio e como resolvê-los.

Foi útil?

Solução 2

Aqui está uma solução que cumpre todos os meus 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;
    }
}

Outras dicas

A melhor expressão para threadsafe código de inicialização (IMHO) é a classe interna preguiçoso. A versão clássica é

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

    static {
      // create the SINGLETON
    }
  }

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

Porque é threadsafe, carregamento lento e (IMHO) elegante.

Agora você quer a capacidade de teste e replaceability. É difícil aconselhar sem saber exatamente o que é, mas a solução mais óbvia seria a injeção de uso dependência, especialmente se você estiver usando Spring e ter um contexto de aplicação de qualquer maneira.

Assim comportamento seu "singleton" 's é representado por uma interface e você simplesmente injetar um desses em suas classes relevantes (ou uma fábrica para produzir um) e, em seguida, você pode, naturalmente, substituí-lo com o que quiser para testes propósitos.

Você pode usar técnicas de IoC mesmo se você não usar um framework IoC (Primavera / Guice / ...). E é na minha opinião a única maneira limpa para evitar um Singleton.

Eu acho que uma solução que você pode usar é fornecer um método protegido que pode ser anulado no teste (uma solução que eu usei antes para testar o código legado).

Então, algo como:

private SomeObject object;

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

Então em sua classe de teste que você pode fazer:

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

Eu tenho que dizer que eu não estou Excessivamente que desejam sobre este padrão, uma vez que expõe o campo de proteção onde você não pode realmente quer isso, mas é útil para você ficar fora de algumas situações em que a injeção não é uma opção

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top