查看本人文章索引請通過http://www.cnblogs.com/seesea125/archive/2012/04/17/2453256.html
我們在編程中最常用的模式就是單例模式了,然而單例模式都用在什么場合?為什么不用靜態方法而要用單例模式呢?要搞清這些問題,需要從靜態方法和非靜態方法的區別和聯系說起。
一、靜態方法常駐內存,非靜態方法只有使用的時候才分配內存?
一般都認為是這樣,並且怕靜態方法占用過多內存而建議使用非靜態方法,其實這個理解是錯誤的。
為什么會這樣,先從內存分配開始說起:
托管堆的定義:對於32位的應用程序來說,應用程序完成進程初始化后,CLR將在進程的可用地址空間分配一塊保留的地址空間,它是進程(每個進程可使用4GB)中可用地址空間上的一塊內存區域,但並不對應任何物理內存,這塊地址空間即是托管堆。
托管堆有分為多個區域,其中最重要的是垃圾回收堆(GC Heap)和加載堆(Loader Heap),GC Heap用於存儲對象實例,受GC管理;Loader Heap又分為High-Frequency Heap、Low-Frequency Heap和Stub Heap,不同的堆上又存儲不同的信息。Loader Heap最重要的信息就是元數據相關的信息,也就是Type對象,每個Type在Loader Heap上體現為一個Method Table(方法表),而Method Table中則記錄了存儲的元數據信息,例如基類型、靜態字段、實現的接口、所有的方法等等。Loader Heap不受GC控制,其生命周期為從創建到AppDomain卸載。(摘自《你必須知道的.Net》)
由此我們就明白了,靜態方法和非靜態方法,在內存里其實都放在Method Table里了,在一個類第一次被加載的時候,它會在Loader Heap里把靜態方法,非靜態方法都寫入Method Table中,而且Loader Heap不受GC控制,所以一旦加載,GC就不會回收,直到AppDomain卸載
由此我們也明白了,靜態方法和非靜態方法,他們都是在第一次加載后就常駐內存,所以方法本身在內存里,沒有什么區別,所以也就不存在”靜態方法常駐內存,非靜態方法只有使用的時候才分配內存“這個結論了。
二、靜態方法和非靜態方法的區別?
在內存中的區別是,非靜態方法在創建實例對象時,因為屬性的值對於每個對象都各不相同,因此在new一個實例時,會把這個實例屬性在GC Heap里拷貝一份,同時這個new出來的對象放在堆棧上,堆棧指針指向了剛才拷貝的那一份實例的內存地址上。而靜態方法則不需要,因為靜態方法里面的靜態字段,就是保存在Method Table里了,只有一份。
因此靜態方法和非靜態方法,在調用速度上,靜態方法速度一定會快點,因為非靜態方法需要實例化,分配內存,但靜態方法不用,但是這種速度上差異可以忽略不計。
三、為什么要有非靜態方法?
早期的結構化編程,幾乎所有的方法都是“靜態方法”,引入實例化方法概念是面向對象概念出現以后的事情了,區分靜態方法和實例化方法不能單單從性能上去理解,創建c++,java,c#這樣面向對象語言的大師引入實例化方法一定不是要解決什么性能、內存的問題,而是為了讓開發更加模式化、面向對象化。這樣說的話,靜態方法和實例化方式的區分是為了解決模式的問題。
接下來繼續思考,如果我們全部用靜態方法,不用非靜態方法,不是一樣能實現功能嗎?是的,沒錯,但是你的代碼是基於對象,而不是面向對象的,因為面向對象的繼承和多態,都是非靜態方法。
第二個原因是為什么不建議都用靜態方法,我們如果多線程的情況下,如果靜態方法使用了一個靜態字段,這個靜態字段可以會被多個線程修改,因此說如果在靜態方法里使用了靜態變量,這就會有線程安全問題,當然了,就算不是多線程,因為靜態字段只有一份,同樣會有被其他地方修改的問題。
從這三點我們得出的結論如下:
一、 什么時候用靜態方法,什么時候使用非靜態方法?
既然靜態方法和實例化方式的區分是為了解決模式的問題,如果我們考慮不需要繼承和多態的時候,就可以使用靜態方法,但就算不考慮繼承和多態,就一概使用靜態方法也不是好的編程思想。
從另一個角度考慮,如果一個方法和他所在類的實例對象無關,那么它就應該是靜態的,否則就應該是非靜態。因此像工具類,一般都是靜態的。
二、 為什么使用單例模式而不用靜態方法?
從面相對象的角度講:
雖然都能實現目的,但是他們一個是基於對象,一個是面向對象的,就像我們不面相對象也能解決問題一樣,面相對象的代碼提供一個更好的編程思想。
如果一個方法和他所在類的實例對象無關,那么它就應該是靜態的,反之他就應該是非靜態的。如果我們確實應該使用非靜態的方法,但是在創建類時又確實只需要維護一份實例時,就需要用單例模式了。
比如說我們在系統運行時候,就需要加載一些配置和屬性,這些配置和屬性是一定存在了,又是公共的,同時需要在整個生命周期中都存在,所以只需要一份就行,這個時候如果需要我再需要的時候new一個,再給他分配值,顯然是浪費內存並且再賦值沒什么意義,所以這個時候我們就需要單例模式或靜態方法去維持一份且僅這一份拷貝,但此時這些配置和屬性又是通過面向對象的編碼方式得到的,我們就應該使用單例模式,或者不是面向對象的,但他本身的屬性應該是面對對象的,我們使用靜態方法雖然能同樣解決問題,但是最好的解決方案也應該是使用單例模式。
從功能上講:單例模式可以控制單例數量;可以進行有意義的派生;對實例的創建有更自由的控制;
三、其他:
數據庫連接能不能做SingleTon?
如果是簡單地把一個connection對象封存在單例對象中,這樣是錯誤的,因此連接池里有多個鏈接可以用,如果使用SingleTon,那在WEB訪問時,就只能用一個數據庫鏈接,那不是死的很慘?
但是鏈接池可以使用單例模式,初始化的時候創建譬如100個connection對象,然后再需要的時候提供一個,用過之后返回到pool中,我們用單例模式,是保證連接池有且只有一個。
再舉個例子,比如DAL層寫好一個調用數據庫表的類,在BLL層應用此類時,如果每次都new創建的話需要頻繁的創建和回收,而DAL層這個類里又沒有和對象相關的值變量,所以不需要每次都new一個,這時候就可以用單例模式來創建這個DAL實例。