方式对懒惰"运行一次"初始化Java与复盖从单元的测试
-
22-07-2019 - |
题
我在找一块代码,它的行为就像一个单独但并不是(因为单一的坏)我在寻找什么必须满足这些目标:
- 线安全
- 简单(理解和使用,即几行的代码。图书馆电话被确定)
- 快速
- 没有一个单一实例;用于测试,它必须能够复盖值(和重置它之后的试验)。
- 当地(所有必要的信息必须在同一个地方)
- 懒惰的(运行只有当值实际上需要)。
- 运行一次(代码右轴必须执行一次只有一次)
例编码:
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+"!");
}
注意的领域是 不 静态的;只有右轴(右侧)的表达是...好的"静态"对某一程度。试验应当能够注入新的价值观为 i
和 greet
暂时的。
还注意到,这段代码概述了我是如何打算使用这种新的编码。随替代runOnce()用任何东西,或将其移至其他一些地方(本构造,也许,或者一个初始化()法或吸气).但小LOC好。
一些背景信息:
我不是在寻找春天,我在找一块代码可以使用的最常见的情况:你需要实现一个接口,并且有永远不会是第二次执行情况,除了用于测试在那里你想要通过在模拟对象。此外,弹簧失败#2,#3和第5:你需要学习的配置语言,则必须设置的应用程序方面的某个地方,它需要一种XML parser而这是不当地(信息传播到地方)。
全球配对象或工厂无法满足该法案,因为#5.
static final
是因为第4(不能改变最终). static
闻起来,因为类装入器的问题,但是你可能会需要它的内部 runOnce()
.我只是喜欢能够避免它在左轴的表达。
一个可能的解决办法可能是使用 里 有一种默认设置这将返回相同的对象。因为我可以把东西放在高速缓存,这也将允许以替代的价值在任何时间。但也许有一个更紧凑、简单的解决方案比里(这再次需要一个XML配置文件等)。
[编辑]我不知道为什么这么多人的列表这一点。这是一个有效的问题和使用情况是很常见的(至少在我的代码)。所以如果你不了解这个问题(或它背后的原因)或者如果你没有回答或者你不在乎为什么列表?:/
[EDIT2]如果你看的应用程序方面的春天,你会发现,超过99%的所有豆只有一个单一的执行。你能有更多,但在实践中,你根本没有。因此,而不是分离接口的实施和配置,我在看一些东西,只有执行(在最简单的情况下),当前()方法和一个或两个线的聪明的代码来初始化的结果对于当前的()进行一次(在第一时间)但在同一时间允许复盖的结果(线的安全,如果可能)。认为它作为一个原子"如果(o==null)o=new O();回o"在那里你可以改写。也许一个AtomicRunOnceReference类的解决方案。
现在,我只是觉得那什么,我们所有拥有和使用每日不是最佳的,有一个令人困惑的解决办法,这将使我们所有人打我们的元首,并说"这是它。"只是因为我们认为,当春天来了围绕几年前,我们意识到我们所有的单独问题来自何处和如何解决这些问题。
解决方案 2
这里是一个解决方案,满足我所有的要求:
/** 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;
}
}
其他提示
最好的成语线程安全的初始化时代码(恕我直言)是懒内类。经典的版本是
class Outer {
class Inner {
private final static SomeInterface SINGLETON;
static {
// create the SINGLETON
}
}
public SomeInterface getMyObject() {
return Inner.SINGLETON;
}
}
因为这是线程安全的,懒惰的装载和(恕我直言)优雅。
现在你想要的可测性和可更换性.很难劝不知道它究竟是什么但最明显的解决办法是使用依赖注射,特别是如果您使用的弹簧和有一个应用程序方面。
这样你的"单独"'s行为是由一个接口,你只需注入一些到你的相关类别(或一家工厂来生产一个)和然后你当然可以替代它与任何你喜欢的用于测试目的。
你可以使用IoC技术,甚至如果你不使用IoC框架(春题之一,因/...).这是在我看来,只有清洁的方式,以避免一个单独的.
我觉得一个解决方案可以使用是提供保护的方法,它可能复盖在测试(一个解决方案我使用之前进行测试的遗产代码)。
所以是这样的:
private SomeObject object;
protected SomeObject getObject() {
if (object == null) {
object = new SomeObject();
}
return object;
}
然后在你的测试类可以做到:
public void setUp() {
MyClassUnderTest cut = new MyClassUserTest() {
@Override
protected SomeObject getObject() }
return mockSomeObject;
}
};
}
我必须说我是不是过分热衷于这种模式,因为它使受保护的领域在那里你不会真的想要它,但它是有用的你的一些情况下,注不是一个选项