1. 有字符串型的枚舉嗎?
UK 在《關(guān)于枚舉的種種》中提到這樣一個問題:
枚舉的成員類型都是數(shù)值型的,如果想做一個字符型的枚舉有什么辦法?
enum colors : string{
red='#ff0000',
}
在展開討論之前,我認為有必要搞清楚另一個問題,上面代碼中的 '#ff0000' 不是字符而是字符串,應(yīng)改成 "#ff0000",于是,UK 的問題也順利成章地改成“想做一個字符串型的枚舉有什么辦法”了。
很坦白地說,.NET 并不支持這樣的字符串型枚舉。另外,也不是所有的數(shù)值類型都能作為枚舉的底層類型,枚舉的底層類型只能是整數(shù)類型,這意味著某天你想定義一個底層類型為 double 的枚舉,你將會收到編譯器的警告信。
UK 的代碼確實提出了這樣一種需求,colors 就像一個枚舉,但其成員的值是一個字符串,于是,我們很容易產(chǎn)生這樣一個疑問:是否能夠模擬出這樣一個枚舉呢?
2. 有辦法模擬出來嗎?
在寫任何代碼之前,讓我們首先思考一下,假如真有這樣一個 Color 類,我們會怎樣使用它?以下是我想到的兩個用法:
1// Code #01
2
3Color c1 = Color.Red;
4string s1 = (string)c1;
5Debug.Assert(s1 == "#FF0000");
6
7Color c2 = (Color)"#00FF00";
8Debug.Assert(c2 == Color.Green);
首先,枚舉是通過其成員而不是構(gòu)造函數(shù)來初始化的,正如上面代碼的第三行。于是,我們應(yīng)該把構(gòu)造函數(shù)設(shè)為私有,同時模擬一些枚舉成員并暴露出來:
// Code #02
public class Color
{
PRivate Color(string value)
{
m_Value = value;
}
private string m_Value;
public static readonly Color Red = new Color("#FF0000");
public static readonly Color Green = new Color("#00FF00");
public static readonly Color Blue = new Color("#0000FF");
}
這樣,Code #01 的第三行就可以工作了。值得提醒的是,Red、Green 和 Blue 等成員的值都是不變的,為了不讓客戶代碼修改它們的值,我把它們設(shè)成 readonly(注意:它們不可以設(shè)為 const,你知道為什么嗎?)。另外,它們應(yīng)該屬于 Color 類而不是某個實例的,于是把它們設(shè)成 static。
接著,Code #01 的第四行說明了 Color 的實例可以強制轉(zhuǎn)換成字符串,這可以通過重載轉(zhuǎn)換運算符做到:
// Code #03
public static explicit Operator string(Color value)
{
return value.m_Value;
}
與 Code #01 的第四行對應(yīng)的是 Code #01 的第七行,它說明了字符串可以強制轉(zhuǎn)換成 Color 實例,這也是通過重載轉(zhuǎn)換運算符做到的:
// Code #04
public static explicit operator Color(string value)
{
switch (value)
{
case "#FF0000":
return Red;
case "#00FF00":
return Green;
case "#0000FF":
return Blue;
default:
throw new InvalidCastException();
}
}
很明顯,并不是所有的字符串都可以轉(zhuǎn)換成 Color 實例,所以,當(dāng)客戶代碼試圖把一個含有非預(yù)期值的字符串轉(zhuǎn)換成 Color 實例時就應(yīng)該拋出 InvalidCastException 了。
誠然,有些人會對 Code #04 很反感,因為它里面有一個 switch!當(dāng)我們使用枚舉時,我們并不只是用它來進行一些值的轉(zhuǎn)換或者獲取其字面值,我們更希望用它來標(biāo)識不同的情況或者類別,并根據(jù)枚舉實例的值來判斷屬于哪一情況或類別。換句話說,當(dāng)我們決定使用枚舉時,我們就注定與條件語句結(jié)下不解之緣了。
最后,這里給出 Color 的完整代碼:
// Code #05
Color#region Color
public class Color
{
private Color(string value)
{
m_Value = value;
}
public static explicit operator Color(string value)
{
switch (value)
{
case "#FF0000":
return Red;
case "#00FF00":
return Green;
case "#0000FF":
return Blue;
default:
throw new InvalidCastException();
}
}
public static explicit operator string(Color value)
{
return value.m_Value;
}
public override string ToString()
{
return m_Value;
}
public static readonly Color Red = new Color("#FF0000");
public static readonly Color Green = new Color("#00FF00");
public static readonly Color Blue = new Color("#0000FF");
private string m_Value;
}
#endregion
或許有人會提出這樣一個問題:我們經(jīng)常會對枚舉進行判等運算,但為什么 Code #05 中既沒有重載 Object.Equals 方法,也沒有提供 == 和 != 運算符呢?細心觀察 Color 的代碼,你會發(fā)現(xiàn)獲取 Color 的實例只有兩種途徑:通過靜態(tài)只讀字段和通過強制轉(zhuǎn)換運算符,但無論你是如何得到 Color 的實例,這些實例最終都是源自 Color 內(nèi)部的靜態(tài)只讀字段。換言之,通過這兩種途徑分別獲得的兩個 Color 實例其實是同一個實例。
3. 模擬方案有什么問題嗎?
我相信,Color 在一定程度上能夠滿足 UK 的需求了,但是,我認為 Color 并不一定能用到實際的應(yīng)用中去。回顧 Code #05,Color 既是一個枚舉也是一個數(shù)據(jù)載體。說它是枚舉,是因為我們刻意把它模擬成枚舉;說它是一個數(shù)據(jù)載體,是因為我們并不僅僅以枚舉的方式來用它,我們更需要的是成員背后所代表的值。這意味著 Color 承擔(dān)的責(zé)任多于一個,違反了單一責(zé)任原則(SRP)。
我懷疑,UK 之所以對 Color 有這樣的期望,或多或少是受到了《關(guān)于枚舉的種種》中枚舉成員的值那部分內(nèi)容的影響,尤其是“為什么需要手動指定枚舉成員的值?”這個問題的答案。如果真是這樣,我必須就該文產(chǎn)生誤導(dǎo)在此向你道歉。現(xiàn)實的情況往往不會像該文的 Code #13 那樣簡單,不但同一類型的顧客(例如白金會員)所能享受到的折扣隨時會發(fā)生變化,而且同一的商品在不同的時期(例如促銷期)的折扣也有可能不同,于是顧客最終所能享受到的折扣可能是一個經(jīng)過復(fù)雜運算的綜合折扣。任何對變化因素的硬編碼都會導(dǎo)致系統(tǒng)的僵化!我建議在閱讀該文這部分內(nèi)容時應(yīng)以研究枚舉的這方面特性為目的。
另一方面,Color 作為一個數(shù)據(jù)載體,它確實弱得可憐。目前它僅包含 R、G、B 三個通道的數(shù)據(jù),如果我想加入 alpha 通道的數(shù)據(jù)呢?這會對它的代碼產(chǎn)生多大的沖擊?如果我希望它能分別為我提取 A、R、G、B 四個通道的數(shù)據(jù)呢?如果我希望實現(xiàn) RGB 和 CMYK 之間的數(shù)據(jù)轉(zhuǎn)換呢?我相信這些問題已經(jīng)足夠讓你頭痛一周了,但當(dāng)你知道人的肉眼能夠識別的顏色約有一千六百萬種,而這些顏色都可以通過 RGB 值來描述并作為顯示器輸出的依據(jù),你的鼠標(biāo)會不會馬上指向瀏覽器右上角的交叉呢?
很明顯,這里所給出的 Color 是經(jīng)不起時間的考驗的,于是我不禁在想,.NET Framework 究竟如何表達 Color 呢?它又是如何滿足這些讓人苦悶的需求呢?
4. .NET Framework 又如何表達 Color 呢?
在 .NET Framework 中,和這里所提到的需求相關(guān)的東西有3個:Color 結(jié)構(gòu)、KnownColor 枚舉和 ColorTranslator 類,它們都位于 System.Drawing 命名空間中。
首先說說 KnownColor 枚舉,它的成員可以分成兩類:一類是有名字的顏色,與它們對應(yīng)的 RGB 值是不變的,如 Indigo 是靛藍;另一類是外觀項目的顏色,與它們對應(yīng)的 RGB 值會隨著用戶的設(shè)置而改變,如 InfoText 是工具提示文本的顏色。
其次是 Color 結(jié)構(gòu)了,它有很多靜態(tài)屬性,這些屬性都是獲取與 KnownColor 枚舉中第一類成員對應(yīng)的 Color 實例。它還提供 FromKnownColor 和 ToKnownColor 方法用于實現(xiàn)它的實例和 KnownColor 枚舉之間的轉(zhuǎn)換。當(dāng)然,由于它是一個數(shù)據(jù)載體,它包含了 A、R、G、B 四個通道的數(shù)據(jù),對它的實例的判等就相當(dāng)于對這些數(shù)據(jù)進行判等,于是它也重載了 == 和 != 運算符。
說了這么多,好像還沒有提到如何從 Color 實例得到 HTML 顏色值,好吧,現(xiàn)在是時候讓 ColorTranslator 類登場了。該類提供 ToHtml 和 FromHtml 方法用于實現(xiàn) Color 實例和 HTML 顏色值的字符串之間的轉(zhuǎn)換。
這三個東西的責(zé)任都很明晰,當(dāng)你需要判斷某個地方用的是否某種顏色時,沒有必要去比較它們的 A、R、G、B 四個通道的數(shù)據(jù),你真正需要的只是一種快而準(zhǔn)的標(biāo)識對比,KnownColor 枚舉恰恰就能滿足這方面的要求;當(dāng)你需要操作某種顏色的數(shù)據(jù)時,你其實并不希望操作一個字符串,Color 結(jié)構(gòu)能讓你輕易提取所需的數(shù)據(jù);當(dāng)你在顏色數(shù)據(jù)和 HTML 顏色值的字符串之間進行轉(zhuǎn)換時,你其實并不太需要一個枚舉來做標(biāo)識。然而,你也有可能結(jié)合它們?nèi)齻€一起使用,下面給出一個例子作為本文的收尾:
// Code #06
string desktopColorValue = ColorTranslator.ToHtml(Color.FromKnownColor(KnownColor.Desktop));
http://www.survivalescaperooms.com/allenlooplee/archive/2006/09/06/496874.html
新聞熱點
疑難解答