学会放下包袱,热爱单例

原文链接   译者:曾维朝

企业应用程序与移动应用程序有着截然不同的要求。你启动一次企业应用程序,它会连续运行数月或数年。另一方面,大部分手机应用可能是被正在无聊排队或者坐公交车的用户启动的,它们经常连续运行不超过几分钟,这就意味着移动应用程序必须即时开启,而启动一个企业应用程序则需要足够长的时间。

对于企业应用,依赖注入和早期验证是非常重要的, Spring为此提供了极大的便利。 但是别欺骗自己,Spring是好,但它不是万金油。尤其在崇尚快速启动、低内存消耗、避免接口的移动开发领域。

企业应用程序的瓶颈几乎都在数据库,在这里或者其它的地方多花费几毫秒并无大碍。而在性能弱得多的移动设备上, 这些时钟周期不仅算在用户等待的时间上,并且会损耗设备的电量。一个简单的事实,使用接口来替换抽象父类,都要慢上1倍。甚至在嵌套调用的构造函数中多传入个参数,稍微多几层的调用,都会产生影响。

选择性延迟加载单例

Java中的类加载是懒惰式的,Java虚拟机只有在类被引用的时候才会将其加载。用户短时间内运行的移动应用一般不会触发加载其内部的所有类。如果用户只是检查未读信息后退出,则写信息相关的类是不必加载的。早期依赖注入打破了这种做法,所有的类在启动时引入。大部分组件都将会被徒劳的初始化,因为其实它们从未被真正的使用。

所以我们在开发移动应用的做法应该效仿Java虚拟机,而不是像Spring那样。要尽可能只在需要的时候创建某个组件,最好的实现方式就是使用单例模式,但不是那种通常的严格的单例模式,而是一个可选的单例。使用公用的构造函数,信任你的用户,并用getSharedFoo()来命名你的获取器(getter),而不是使用getInstance()。我使用URL缓存组件的例子给你示范下。

[code]

public class URLCache {

private static URLCache sharedCache;

public static URLCache getSharedURLCache() {

synchronized (URLCache.class) {

if (sharedCache == null) {

sharedCache = new URLCache();

}

}

return sharedCache;

}

public URLCache() {

// More code…

}

// Allot more code here…

}

[/code]

在我们想象中的HTTP提供者(HTTPProvider)中使用这个共享的URL缓存(URLCache)组件,将会是超级简单的, 但不是强制的:

[code]
public class HTTPProvider {

public InputStream inputStreamForURL(Stringurl) {

URLCache cache =URLCache.getSharedURLCache();

// Use the cache…

}

}
[/code]

这里最大的好处就是,如果这个应用程序的代码路径从未执行到尝试打开一个输入流,那么URLCache的cache对象就永远不必创建。用来读取缓存索引、验证、等等的几百毫秒时间被节省了。

 

但是测试呢?

对于单元测试和mocking组件,单例不是不好吗? 他们以前是不好,如今我们有PowerMock,你真的应该用它。如果我们只是稍微改了下单例模式,而且可以外部设置共享的组件,那就连PowerMock实际上也不需要了:

[code]
public class URLCache {

private static URLCache sharedCache;

public static void setSharedURLCache(URLCachecache) {

synchronized (URLCache.class) {

sharedCache = cache;

}

}

publicstatic URLCache getSharedURLCache() {

synchronized (URLCache.class) {

if (sharedCache == null) {

sharedCache = new URLCache();

}

}

return sharedCache;

}

 

// Allot more code here…

[/code]

上述添加的那小段代码让我们可以在装入单元测试类时设置自己的mockcache对象。简单示例如下:

[code]
public class SomeTest extendsTestCase {

public void setUp() {

URLCache.setSharedURLCache(newNoOpURLCache());

}

public void testRemoteResource() {

assertNotNull(HTTPProvider.getSharedHTTPProvider().inputStreamForURL(TEST_URL));

}

}
[/code]

如果应用程序有特别的需求, 我们还可以显式的重载一个单例。这些需求也许是低带宽下的移动终端数据连接的一个更先进的缓存方式, 或是在问题多多的JavaME平台上的一个特殊实现。

 

要点

  • 不要惧怕单例,它是个非常好的延迟创建的方式,可以大大改善移动应用程序的启动时间和内存消耗。
  • 避免使用接口,它们比类至少要慢1倍,而且接口不可以有用于获取延迟创建的组件的静态方法。
  • 不要强制使用单例模式,为了测试和特殊情况的易于实现,永远使用可选单例。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 学会放下包袱,热爱单例

  • Trackback 关闭
  • 评论 (5)
    • 戈戈
    • 2014/09/22 10:41上午

    如果接口中只放了常量和枚举,然后在需要使用这些常量的类中现实该接口,会对性能有影响吗?

    • test
    • 2014/09/22 2:21下午

    这个单例模式写的很不好

      • 匿名
      • 2014/09/25 11:07上午

      我想他这只是提供了另一种思路

    • Six.
    • 2014/09/23 10:43上午

    这个单例写的有问题。public class URLCache {

    private static volete URLCache sharedCache;

    public static void setSharedURLCache(URLCachecache) {

    synchronized (URLCache.class) {

    sharedCache = cache;

    }

    }

    publicstatic URLCache getSharedURLCache() {

    if(sharedCache!=null)return sharedCache ;
    synchronized (URLCache.class) {

    if (sharedCache == null) {

    sharedCache = new URLCache();

    }

    }

    return sharedCache;

    }

    • 冬日阳光
    • 2015/08/14 11:57上午

    《java并发编程实战》中对单例的编写讲解是很清楚的。建议作者看一下。

return top