曾經一度為格式化輸出而困惑,看著滿天遍野的結構,都不敢去輕易觸動。只能使用最安全,但是 低能的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]