997
阿裏雲
技術社區[雲棲]
學會放下包袱,熱愛單例
企業應用程序與移動應用程序有著截然不同的要求。你啟動一次企業應用程序,它會連續運行數月或數年。另一方麵,大部分手機應用可能是被正在無聊排隊或者坐公交車的用戶啟動的,它們經常連續運行不超過幾分鍾,這就意味著移動應用程序必須即時開啟,而啟動一個企業應用程序則需要足夠長的時間。
對於企業應用,依賴注入和早期驗證是非常重要的, Spring為此提供了極大的便利。 但是別欺騙自己,Spring是好,但它不是萬金油。尤其在崇尚快速啟動、低內存消耗、避免接口的移動開發領域。
企業應用程序的瓶頸幾乎都在數據庫,在這裏或者其它的地方多花費幾毫秒並無大礙。而在性能弱得多的移動設備上, 這些時鍾周期不僅算在用戶等待的時間上,並且會損耗設備的電量。一個簡單的事實,使用接口來替換抽象父類,都要慢上1倍。甚至在嵌套調用的構造函數中多傳入個參數,稍微多幾層的調用,都會產生影響。
選擇性延遲加載單例
Java中的類加載是懶惰式的,Java虛擬機隻有在類被引用的時候才會將其加載。用戶短時間內運行的移動應用一般不會觸發加載其內部的所有類。如果用戶隻是檢查未讀信息後退出,則寫信息相關的類是不必加載的。早期依賴注入打破了這種做法,所有的類在啟動時引入。大部分組件都將會被徒勞的初始化,因為其實它們從未被真正的使用。
所以我們在開發移動應用的做法應該效仿Java虛擬機,而不是像Spring那樣。要盡可能隻在需要的時候創建某個組件,最好的實現方式就是使用單例模式,但不是那種通常的嚴格的單例模式,而是一個可選的單例。使用公用的構造函數,信任你的用戶,並用getSharedFoo()來命名你的獲取器(getter),而不是使用getInstance()。我使用URL緩存組件的例子給你示範下。
01 |
public class URLCache { |
03 |
private static URLCache sharedCache; |
05 |
public static URLCache getSharedURLCache() { |
07 |
synchronized (URLCache.class) {
|
09 |
if (sharedCache == null) {
|
11 |
sharedCache = new URLCache();
|
27 |
// Allot more code here... |
在我們想象中的HTTP提供者(HTTPProvider)中使用這個共享的URL緩存(URLCache)組件,將會是超級簡單的, 但不是強製的:
01 |
public class HTTPProvider { |
03 |
public InputStream inputStreamForURL(Stringurl) { |
05 |
URLCache cache =URLCache.getSharedURLCache();
|
這裏最大的好處就是,如果這個應用程序的代碼路徑從未執行到嚐試打開一個輸入流,那麼URLCache的cache對象就永遠不必創建。用來讀取緩存索引、驗證、等等的幾百毫秒時間被節省了。
但是測試呢?
對於單元測試和mocking組件,單例不是不好嗎? 他們以前是不好,如今我們有PowerMock,你真的應該用它。如果我們隻是稍微改了下單例模式,而且可以外部設置共享的組件,那就連PowerMock實際上也不需要了:
01 |
public class URLCache { |
03 |
private static URLCache sharedCache; |
05 |
public static void setSharedURLCache(URLCachecache) { |
07 |
synchronized (URLCache.class) {
|
15 |
publicstatic URLCache getSharedURLCache() { |
17 |
synchronized (URLCache.class) {
|
19 |
if (sharedCache == null) {
|
21 |
sharedCache = new URLCache();
|
33 |
// Allot more code here... |
上述添加的那小段代碼讓我們可以在裝入單元測試類時設置自己的mockcache對象。簡單示例如下:
01 |
public class SomeTest extendsTestCase { |
05 |
URLCache.setSharedURLCache(newNoOpURLCache());
|
09 |
public void testRemoteResource() { |
11 |
assertNotNull(HTTPProvider.getSharedHTTPProvider().inputStreamForURL(TEST_URL));
|
如果應用程序有特別的需求, 我們還可以顯式的重載一個單例。這些需求也許是低帶寬下的移動終端數據連接的一個更先進的緩存方式, 或是在問題多多的JavaME平台上的一個特殊實現。
要點
- 不要懼怕單例,它是個非常好的延遲創建的方式,可以大大改善移動應用程序的啟動時間和內存消耗。
- 避免使用接口,它們比類至少要慢1倍,而且接口不可以有用於獲取延遲創建的組件的靜態方法。
- 不要強製使用單例模式,為了測試和特殊情況的易於實現,永遠使用可選單例。
- 轉載自 並發編程網 - ifeve.com
最後更新:2017-05-23 16:32:23