在工作中,我需要寫(xiě)一個(gè)話(huà)單轉換工具,在寫(xiě)這個(gè)工具的過(guò)程中,發(fā)現整個(gè)實(shí)現恰恰可以說(shuō)是策略模式最好的體現。用這個(gè)例子來(lái)說(shuō)明策略模式的應用,最合適不過(guò)了。
話(huà)單轉換工具的目的:將某個(gè)服務(wù)提供商的話(huà)單文本文件,轉換為另一個(gè)服務(wù)提供商的話(huà)單文本文件。如將聯(lián)通的話(huà)單格式轉換為移動(dòng)的話(huà)單格式。
話(huà)單轉換工具的要求:能夠實(shí)現多個(gè)服務(wù)提供商話(huà)單文本文件的互相轉換。
我們首先來(lái)分析一下任務(wù)。首先,各種服務(wù)提供商的話(huà)單格式,無(wú)疑是不相同的。例如,話(huà)單采集后,字段的順序,字段的寬度以及字段間的分割符,都不相同。但是,從總體上來(lái)說(shuō),話(huà)單的表現形式是大致相同的,這位我們實(shí)現話(huà)單轉換提供了一個(gè)技術(shù)上可行的前提。
所謂話(huà)單轉換,就是需要將一個(gè)話(huà)單文本文件讀出,然后對每一行的字符串進(jìn)行識別后,再轉換為符合相對應的服務(wù)提供商標準的話(huà)單文本文件。操作很簡(jiǎn)單,就是文本文件的讀寫(xiě)而已,不同的就在于轉換的方法。根據服務(wù)提供商標準的不同,我們應該為每一種轉換提供相對應的算法。而所謂策略模式,正是對算法的包裝和抽象,將算法的責任和其本身分離。所以,我們現在要做的工作就是將轉換話(huà)單的算法抽象出來(lái)。
根據工具的要求,話(huà)單轉換應該包括3個(gè)方法:
1、將文件讀出的一行字符串轉換為對應的話(huà)單對象;
2、將一種話(huà)單對象轉換為另一種話(huà)單對象;
3、將話(huà)單對象轉換為字符串,以方便寫(xiě)入話(huà)單文本文件;
根據以上的分析,我們還需要為不同的話(huà)單格式建立相應的對象。例如:網(wǎng)通、聯(lián)通和移動(dòng)的話(huà)單格式對象分別為:
public class CNCCdr
{
//網(wǎng)通話(huà)單格式對象的公共屬性;
}
public class CUCCdr
{
//聯(lián)通話(huà)單格式對象的公共屬性;
}
public class CMCdr
{
//移動(dòng)話(huà)單格式對象的公共屬性;
}
接下來(lái)就應該實(shí)現話(huà)單轉換的算法了。首先需要將算法進(jìn)行抽象,而進(jìn)行抽象的最佳選擇莫過(guò)于使用接口,例如我們定義一個(gè)用于話(huà)單轉換的接口ICdrConvert:
public interface ICdrConvert
{
}
按照如前的分析,在接口中應該包括三個(gè)轉換方法。但是現在有個(gè)問(wèn)題,就是轉換的話(huà)單對象。由于方法在接口中,為一個(gè)抽象。而話(huà)單對象可能有多種,具體轉換為何種話(huà)單對象,需要到具體實(shí)現時(shí)才能決定。因此,在接口方法中,無(wú)論返回類(lèi)型,還是傳入參數,涉及到的話(huà)單對象只能是抽象的。也許我們可以考慮System.Object來(lái)表示話(huà)單對象,但更好的辦法是為所有的話(huà)單對象也提供一個(gè)抽象接口。
由于從目前的分析來(lái)看,抽象話(huà)單對象并沒(méi)有一個(gè)公共的方法,所以這個(gè)抽象的話(huà)單接口,是一個(gè)標識接口:
public interface ICdrRecord
{
}
現在,可以對轉換接口的方法進(jìn)行定義了:
public interface ICdrConvert
{
ICdrRecord Convert(string record);
ICdrRecord Convert(ICdrRecord record);
string Convert(ICdrRecord record);
}
自然,這樣的接口定義無(wú)法通過(guò)編譯。為什么呢?是因為第二個(gè)方法的簽名與第三個(gè)方法的簽名重復了(方法的簽名和返回類(lèi)型無(wú)關(guān))。因此,我們需要為第三個(gè)方法修改名字。
但我們仔細想想,第三個(gè)方法的轉換在轉換接口中是必要的嗎?該方法的任務(wù)是將一個(gè)話(huà)單對象轉換為string類(lèi)型。實(shí)際上這個(gè)責任,并不需要專(zhuān)門(mén)的轉換對象來(lái)完成,而應屬于話(huà)單對象本身的責任。再想想.Net中,所有對象均派生于System.Object,而object類(lèi)型均提供了ToString()方法。
從設計的角度來(lái)看,最好的辦法,是在具體的話(huà)單對象中override System.Object的ToString()方法,而不是在轉換對象中,提供該轉換算法。
不過(guò)考慮到,在話(huà)單處理中,更多地會(huì )調用抽象話(huà)單接口類(lèi)型的對象,也許我們將ToString()方法抽象到接口ICdrRecord中會(huì )更好。
public interface ICdrRecord
{
string ToString();
}
public interface ICdrConvert
{
ICdrRecord Convert(string record);
ICdrRecord Convert(ICdrRecord record);
}
而具體的話(huà)單對象,就應該實(shí)現ICdrRecord接口了。因為各話(huà)單對象均繼承了System.Object,則間接地繼承了object對象的ToString()方法,所以,話(huà)單對象應該重寫(xiě)該方法:
public class CNCCdr: ICdrRecord
{
//網(wǎng)通話(huà)單格式對象的公共屬性;
//重寫(xiě)object的ToString()方法,同時(shí)也實(shí)現了接口ICdrRecord的ToString()方法;
public override string ToString()
{
//實(shí)現具體的內容;
}
}
public class CUCCdr: ICdrRecord
{
//聯(lián)通話(huà)單格式對象的公共屬性;
//重寫(xiě)object的ToString()方法,同時(shí)也實(shí)現了接口ICdrRecord的ToString()方法;
public override string ToString()
{
//實(shí)現具體的內容;
}
}
public class CMCdr: ICdrRecord
{
//移動(dòng)話(huà)單格式對象的公共屬性;
//重寫(xiě)object的ToString()方法,同時(shí)也實(shí)現了接口ICdrRecord的ToString()方法;
public override string ToString()
{
//實(shí)現具體的內容;
}
}
下面就是關(guān)鍵的實(shí)現了。由于我們已經(jīng)為轉換算法進(jìn)行了抽象,因此根據策略模式來(lái)實(shí)現具體的轉換算法,就是水到渠成的事情了。實(shí)現代碼之前,先來(lái)看看UML類(lèi)圖:

注意看橙色部分,這一部分即為策略模式的主體。接口ICdrConvert為抽象策略角色,類(lèi)CNCToCUC,CUCToCM為具體策略角色,它們分別實(shí)現了將網(wǎng)通話(huà)單轉換為聯(lián)通話(huà)單,聯(lián)通話(huà)單轉換為中國移動(dòng)話(huà)單的算法。根據實(shí)際需要,還可以添加多個(gè)類(lèi)似的具體策略角色,并實(shí)現ICdrConvert接口:
public class CNCToCUC:ICdrConvert
{
public ICdrRecord Convert(string record)
{
//實(shí)現具體的轉換算法;
}
public ICdrRecord Convert(ICdrRecord record)
{
//實(shí)現具體的轉換算法;
}
}
類(lèi)CUCToCM的實(shí)現相似,不再重復。
那么通過(guò)策略模式實(shí)現,究竟有什么好處呢?請大家注意上圖的CdrOp類(lèi)。該類(lèi)是抽象類(lèi),它提供了一個(gè)構造函數,可以傳遞ICdrConvert對象:
public abstract class CdrOp
{
protected ICdrConvert _convert;
protected string _sourceFileName;
protected string _targetFileName;
public CdrOp(string soureFileName,string targetFileName,ICdrConvert convert)
{
_sourceFileName = soureFileName;
_targetFileName = targetFileName;
_convert = convert;
}
public abstract void HandleCdr();
}
類(lèi)CdrFileOp繼承了抽象類(lèi)CdrOp:
public class CdrFileOp
{
public override void HandleCdr()
{
Read();
Write();
}
private string Read()
{
using (StreamReader sd = new StreamReader(_sourceFileName))
{
ICdrRecord cdr = null;
string line;
while ((line = sd.ReadLine()) != null)
{
//首先調用ICdrRecord Convert(string record)方法;
//再調用ICdrRecord Convert(ICdrRecord record)方法;
//至于實(shí)現的是何種轉換,由構造函數傳入的ICdrConvert對象決定;
cdr = _convert.Convert(_convert.Convert(line));
_list.Add(cdr);
}
}
}
private void Write()
{
using (StreamWriter sw = new StreamWriter(_targetFileName,true))
{
sw.Write(head.ToString());
foreach (ICdrRecord record in _list)
{
sw.Write(record.ToString());
}
}
}
private ArrayList _list = new ArrayList();
}
這個(gè)類(lèi),實(shí)現了抽象類(lèi)CdrOp的HandleCdr()方法。但具體的實(shí)現細節則是在私有方法Read()和Write()中完成的(根據實(shí)際情況,也可以把Read和Write方法作為公共抽象方法或保護方法,放到抽象類(lèi)CdrOp中,而在抽象類(lèi)CdrOp中具體提供HandleCdr方法的實(shí)現,該方法調用Read和Write方法,這樣就使用了模版方法模式)。
注意看Read和Write方法中,沒(méi)有一個(gè)具體類(lèi)。不管是話(huà)單對象,還是話(huà)單轉換對象,均是抽象接口對象。尤其是在Read()方法中,調用了_Convert的Convert方法:
cdr = _convert.Convert(_convert.Convert(line));
內部的Convert方法,即_convert.Convert(line),是將讀出來(lái)的字符串轉換為ICdrRecord對象,然后通過(guò)調用_convert.Convert(ICdrRecord record)方法,再將該對象轉換為另一種話(huà)單格式對象,但類(lèi)型仍然屬于ICdrRecord。
那么在這些轉換過(guò)程中,究竟轉換成了哪一種話(huà)單格式對象呢?這是由_convert字段來(lái)決定的。而這個(gè)對象則是由構造函數的參數中傳遞進(jìn)來(lái)的。
同樣的道理,在Write()方法中,大家也可以看到為所有話(huà)單對象抽象為一個(gè)接口ICdrRecord的好處。通過(guò)ICdrRecord調用ToString()方法,避免了在CdrFileOp中引入具體對象。要知道,程序一旦引入具體對象,則耦合性就高了。一旦需求發(fā)生改變,就需要對編碼進(jìn)行修改。
有了以上的架構,客戶(hù)端調用就非常方便了:
public class Client
{
public static void Main()
{
string sourceFileName = “c:\CNCCdr.txt”;
string target1= “c:\CUCCdr.txt”;
string target2= “c:\CMCdr.txt”;
//將網(wǎng)通話(huà)單轉換為聯(lián)通話(huà)單;
ICdrOp op1 = new CdrFileOp(sourceFileName,target1,new CNCToCUC());
op1.HandleCdr();
//將剛才轉換生成的聯(lián)通話(huà)單轉換為移動(dòng)話(huà)單;
ICdrOp op2 = new CdrFileOp(target1,target2,new CUCToCM());
op2.HandleCdr();
}
}
當然,我們還可以引入工廠(chǎng)模式來(lái)創(chuàng )建CdrFileOp對象?;蛘邔CdrConvert對象設置為CdrFileOp的公共屬性,而非通過(guò)構造函數來(lái)傳入。然而,通過(guò)本文,策略模式的精要已經(jīng)體現得淋漓盡致了。

