曾經一度為格式化輸出而困惑,看著滿天遍野的結構,都不敢去輕易觸動。只能使用最安全,但是 低能的object.ToString()方法。終於忍受不了這種窘困的處境,下力氣研究一番,也算是有點心得,希望和大家交流一下。
鑒於該格式化輸出的結構過於繁瑣,我不希望文章陷入條款的解釋,於是,我從一個實際問題入手,一步一步地介紹格式化輸出的概念,三個接口的意義,以及使用接口的一種模式(Pattern)。
1. PhoneNumber 類
假設我們有如下的一個類,用於存儲我們的手機號碼。該類提供了簡單的ToString()實現,即直接返回含有短線(-)的電話號碼。
class PhoneNumber
...{
private string _number = "139-0814-2314";
public PhoneNumber() ...{ }
public PhoneNumber(string number)
...{
_number = number;
}
public string Number
...{
get ...{ return _number; }
set ...{ _number = value; }
}
public override string ToString()
...{
return _number.ToString();
}
}
2. 提供去掉短線的輸出格式 (IFormattable)
現在,我們希望能輸出不含有短線的電話號碼。一種最簡單的辦法,就是提供一個屬性或者方法來實現。然而,我們希望能夠像以貨幣格式輸出整數一樣,能夠用類似的方法,以不含有短線的方式,輸出電話號碼。即:
//我們通過格式字符串(formatString),可以以貨幣格式輸出整數。
int d = 100;
string val = string.Format("{0:D}",d);
Console.WriteLine(val);
//結果是(結果會因為區域設置有不同,但結構是類似的,即在數字前會出現貨幣符號)
//¥100
//我們希望能夠通過如下的方式,輸出不含短線的電話號碼。
PhoneNumber pn = new PhoneNumber();
string val = string.Format("{0:R}",pn);
Console.WriteLine(val);
//期望的結果是:
//13908142314
我們需要作的,就是實現IFormattable接口:
class PhoneNumber : IFormattable
...{
//同上
public override string ToString()
...{
return ToString(null, null);
}
IFormattable Members#region IFormattable Members
public string ToString(string format, IFormatProvider formatProvider)
...{
string _val = string.Empty;
switch (format)
...{
case "R":
_val = _number.Replace("-", "");
break;
default:
_val = _number.ToString(formatProvider);
break;
}
return _val;
}
#endregion
}
現在,一切工作的非常良好,我們的PhoneNumber類也非常順利的發布出去了。一切看起來都非常不錯!
3. 提供隱藏部分號碼的輸出格式 (IFormatProvider, ICustomFormatter)
在使用中,用戶打電話問訊我們,能否提供隱藏部分號碼的輸出格式,即13908142314輸出為139****2314。“這個容易,我們馬上更新”,放下電話,我們在switch中加入了一項,就能解決問題。可是,這樣做,一個潛在的問題是,每次我們得到新的需求,都需要修改我們的PhoneNumber類,這樣做,每次用戶都需要重新編譯PhoneNumber類,以及所有和它相關的類,這樣是一個高風險,違背軟件設計原則的解決辦法。於是,我們應該采用如下的模式來實現擴展的需求:
首先,實現一個單獨的HideFormat類,實現兩個接口。這個類就是我們自己定義的格式規則。
class HideFormat : IFormatProvider, ICustomFormatter
...{
IFormatProvider Members#region IFormatProvider Members
public object GetFormat(Type formatType)
...{
if (typeof(ICustomFormatter) == formatType)
...{
return this;
}
; return null;
}
#endregion
ICustomFormatter Members#region ICustomFormatter Members
public string Format(string format, object arg, IFormatProvider formatProvider)
...{
// Now the formatProvider must have a ICustomFormatter
if (arg == null)
...{
throw new ArgumentException("The object should not be null");
}
// Describe the formatter rules here
string _val;
switch (format)
...{
case "H":
_val = arg.ToString().Replace("-", "");
_val = _val.Substring(0, 3) + "****" + _val.Substring(7, 4);
break;
default:
_val = arg.ToString();
break;
}
return _val;
}
#endregion
}
然後,修改我們的PhoneNumber類(只需要修改一次)。
class PhoneNumber : IFormattable
...{
//同上
IFormattable Members#region IFormattable Members
public string ToString(string format, IFormatProvider formatProvider)
...{
string _val = string.Empty;
//如果傳入的是我們自己定義的格式,則使用這個格式去處理我們的格式化請求。
if (formatProvider != null)
...{
ICustomFormatter formatter = formatProvider.GetFormat(typeof(ICustomFormatter))
as ICustomFormatter;
if (null != formatter)
...{
return formatter.Format(format, this, formatProvider);
}
}
//如果傳入的格式為空,或者是系統默認的格式,則按照系統默認的方式去處理我們的格式化請求。
switch (format)
...{
case "R":
_val = _number.Replace("-", "");
break;
default:
_val = _number.ToString(formatProvider);
break;
}
return _val;
}
#endregion
}
然後,為了輸出隱藏部分號碼的格式。我們只需要做如下的調用:
PhoneNumber pn = new PhoneNumber();
SecureFormat sf = new SecureFormat();
string _val;
_val = string.Format(sf,"{0:H}", pn);
Console.WriteLine(_val); //輸出結果是139****2314
_val = pn.ToString("H",sf);
Console.WriteLine(_val); //輸出結果是139****2314
自此,我們完成了所有的任務和更新。最後,讓我們看看擴展性。如果以後我們接到了新的格式申請,我們需要作的,就是實現一個類似SecureFormat的自定義的格式類,然後交給用戶使用。不需要我們修改PhoneNumber的任何實現。這個解決方案,很好的體現了開放-閉合(Open-Close Principle)的軟件設計原則。
作者:Shu Liu(本人系.Net菜鳥,希望大家給予意見和建議)
Mail: [email protected]