Maneiras para preguiçoso “Executar uma vez” inicialização em Java com substituição de testes de unidade
-
22-07-2019 - |
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:
- seguro de rosca
- Simples (Entenda & uso, ou seja, algumas linhas de código. Chamadas de biblioteca são OK)
- rápido
- Não um singleton; para testes, deve ser possível para substituir o valor (e redefini-la após o teste).
- Local (todas as informações necessárias devem estar em um lugar)
- preguiçoso (executar somente quando o valor é realmente necessário).
- 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.
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