這是我寫模式設(shè)計的第二篇,首先來說說設(shè)計模式的分類。
基本的23種設(shè)計模式從目的上可分為三種:
1、 創(chuàng)建型(Creational)模式:負(fù)責(zé)對象創(chuàng)建。
2、 結(jié)構(gòu)型(Structural)模式:處理類與對象間的組合,可以解決一些繼承依賴性的問題
3、 行為型(Behavioral)模式:類與對象交互中的職責(zé)分配,可以解決組件間如何和交互,隔離變化。
下面來說說單件模式:
首先說說單件模式產(chǎn)生的動機,也就是為什么會出現(xiàn)單件模式。有一些類在系統(tǒng)中只存在一個實例才能確保他們的邏輯正確性以及良好的效率。這時我想到我遇到的一個問題。我曾經(jīng)遇到一個WinForm程序,運行后出現(xiàn)一個登陸框,輸入用戶名密碼后點擊登陸,然后顯示一個登陸后的界面。但是點擊登陸后,程序要做一些操作,由于這段操作用時相對較長,在不經(jīng)意時,我有點擊了一次登陸按鈕,最后出現(xiàn)了兩個對話框。如:我現(xiàn)在有兩個Form窗體Form1和Form2。Form1上有一個按鈕用來打開Form2并隱藏自己。我們可以這樣寫:
private void button1_Click(object sender, System.EventArgs e)
{
Form2 form = new Form2();
form.Show();
this.Hide();
}
如果我們在顯示Form2前由一些比較耗時的操作。如:我們讓線程的沉睡10秒在顯示Form2,當(dāng)我們在線程沉睡時繼續(xù)點擊Form1上的Button,有可能就會出現(xiàn)兩個Form2的窗體。(我試過可以出現(xiàn)兩個Form2,如果你有心試但沒事出來別拿西紅柿砍我,哈哈)
private void button1_Click(object sender, System.EventArgs e)
{
Thread.Sleep(10000);
Form2 form = new Form2();
form.Show();
this.Hide();
}
這種情況出現(xiàn)不能怪客戶多點了一下,也不能說是編譯器不夠智能,應(yīng)該是我們程序上的Bug,我想這種情況用單件模式應(yīng)該可以解決。
單件模式的使用意圖就是:保證一個類僅有一個實例,并提供一個該實例全局的訪問點(這句話當(dāng)然不是我先說的,是引用Gof在《設(shè)計模式》中的一句話)
那類的設(shè)計者如何繞過常規(guī)的構(gòu)造器來實現(xiàn)單件模式呢?下面就來談?wù)剢渭J降膶崿F(xiàn)。
單件模式在結(jié)構(gòu)上使用了景泰方法來約束構(gòu)造器(也就是構(gòu)造函數(shù))創(chuàng)建對象。
在單線程的情況下:私有化構(gòu)造函數(shù),使類的使用者調(diào)用不到這個構(gòu)造函數(shù)來new一個實例。類型中可以自己new一個實例。類中創(chuàng)建一個靜態(tài)私有變量和Static公有屬性。在公有屬性中實現(xiàn)此類的實例化。這樣在第一次請求時創(chuàng)建此對象。代碼如下:
class Singleton
{
private static Singleton _instance;
private Singleton(){}
public static Singleton f_Instance
{
get
{
if(_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
我在main函數(shù)中寫入如下程序來查看一下這樣寫是否有效:
static void
{
Singleton t1 = Singleton.f_Instance;
Singleton t2 = Singleton.f_Instance;
Console.Write(object.ReferenceEquals(t1,t2));
Console.Read();
}
控制臺顯示為True,開來還是有效的。當(dāng)然在Main中我也試過這樣寫:Singleton t1 = new Singleton(),編譯時告訴我Singleton()不可訪問(當(dāng)然,人家是私有的,不是自家人當(dāng)然不見)
這種單線程下的單件模式有幾點要注意:
1、 構(gòu)造器私有化(如果要此類被繼承,可以用protected聲明構(gòu)造器)
2、 不要支持IClinieable接口,因為會導(dǎo)致多個對象實例的出現(xiàn)
3、 不能支持序列化
4、 單件模式只考慮了對象創(chuàng)建的管理,沒有考慮對象的銷毀管理(創(chuàng)建自己的對象,銷毀的事交給垃圾回收器吧)
5、 不能應(yīng)對多線程環(huán)境,因為會導(dǎo)致多個對象實例的出現(xiàn)
那在多線程下如何實現(xiàn)呢?代碼如下:
class SingletonMuli//多線程Singleton模式
{
private static volatile SingletonMuli _instance; //volatile是為了讓編譯器對此代碼編譯后的位置不進行調(diào)整
private SingletonMuli(){}
private static object lockHelper = new object(); //輔助器,不參與對象構(gòu)建
public static SingletonMuli f_Instance
{
get
{
if(_instance == null)
{
lock(lockHelper)
{
if(_instance == null) //雙檢查
{
_instance = new SingletonMuli();
}
}
}
return _instance;
}
}
}
當(dāng)然還有一些更簡單的實現(xiàn)方法,如:
class Singleton1//可以用在多線程環(huán)境
{
public static readonly Singleton1 _instance = new Singleton1();
private Singleton1(){}
}
其中要提到的是在_instance私有字段的實例化叫做“內(nèi)聯(lián)初始化”。內(nèi)聯(lián)初始化是指在聲明時。
實際上面的代碼上相當(dāng)于如下代碼:
Public static readonly Singleton1 _instance;
Static Singleton() //靜態(tài)構(gòu)造函數(shù)
{
_instance = new Singleton(); //私有構(gòu)造器
}
Private Singleton(){}
內(nèi)聯(lián)初始化時會先執(zhí)行靜態(tài)構(gòu)造器,如果沒有靜態(tài)構(gòu)造函數(shù),系統(tǒng)會默認(rèn)一個。在訪問此靜態(tài)字段時執(zhí)行靜態(tài)構(gòu)造器生成。靜態(tài)構(gòu)造器保證了在多線程時只有一個線程執(zhí)行,自動加鎖。
當(dāng)然,第二種實現(xiàn)方式也有一些缺點,如:靜態(tài)構(gòu)造器必須是私有的、無參的。不過也可以用其他的方式解決這類問題。如可以用方法屬性實現(xiàn)擴展或修改私有構(gòu)造器。
現(xiàn)在我們可以回來看看我開始說的那兩個Form的問題,我們現(xiàn)在可以這樣實現(xiàn):
private static Form2 form;
private void button1_Click(object sender, System.EventArgs e)
{
Thread.Sleep(10000);
object lockhelp = new object();
if(form == null)
{
lock(lockhelp)
{
if(form == null)
{
form = new Form2();
form.Show();
}
}
}
this.Hide();
}
這樣問題就解決了(我是沒有點出來第二個Form2,如果那位點出來了,給我發(fā)Email,我請她/他在天津的烤鴨)
單件模式實際上是利用控制對象創(chuàng)造過程來控制對象的創(chuàng)造個數(shù)的方法,我們可以對其進行擴展,不是讓他只生成一個對象,可以讓他只生成幾個對象,這樣可以實現(xiàn)對象池。
單件模式的核心是:如何控制用戶使用new對一個類的實例構(gòu)造器的任意調(diào)用。