分享

策略模式的应用实践

 ljjzlm 2006-08-04

策略模式的应用实践

Filed under: — bruce zhang @ 5:11 pm

在工作中,我需要写一个话单转换工具,在写这个工具的过程中,发现整个实现恰恰可以说是策略模式最好的体现。用这个例子来说明策略模式的应用,最合适不过了。

话单转换工具的目的:将某个服务提供商的话单文本文件,转换为另一个服务提供商的话单文本文件。如将联通的话单格式转换为移动的话单格式。

话单转换工具的要求:能够实现多个服务提供商话单文本文件的互相转换。

我们首先来分析一下任务。首先,各种服务提供商的话单格式,无疑是不相同的。例如,话单采集后,字段的顺序,字段的宽度以及字段间的分割符,都不相同。但是,从总体上来说,话单的表现形式是大致相同的,这位我们实现话单转换提供了一个技术上可行的前提。

所谓话单转换,就是需要将一个话单文本文件读出,然后对每一行的字符串进行识别后,再转换为符合相对应的服务提供商标准的话单文本文件。操作很简单,就是文本文件的读写而已,不同的就在于转换的方法。根据服务提供商标准的不同,我们应该为每一种转换提供相对应的算法。而所谓策略模式,正是对算法的包装和抽象,将算法的责任和其本身分离。所以,我们现在要做的工作就是将转换话单的算法抽象出来。

根据工具的要求,话单转换应该包括3个方法:
1、将文件读出的一行字符串转换为对应的话单对象;
2、将一种话单对象转换为另一种话单对象;
3、将话单对象转换为字符串,以方便写入话单文本文件;

根据以上的分析,我们还需要为不同的话单格式建立相应的对象。例如:网通、联通和移动的话单格式对象分别为:
public class CNCCdr
{
 //网通话单格式对象的公共属性;
}
public class CUCCdr
{
 //联通话单格式对象的公共属性;
}
public class CMCdr
{
 //移动话单格式对象的公共属性;
}

接下来就应该实现话单转换的算法了。首先需要将算法进行抽象,而进行抽象的最佳选择莫过于使用接口,例如我们定义一个用于话单转换的接口ICdrConvert:
public interface ICdrConvert
{
}
按照如前的分析,在接口中应该包括三个转换方法。但是现在有个问题,就是转换的话单对象。由于方法在接口中,为一个抽象。而话单对象可能有多种,具体转换为何种话单对象,需要到具体实现时才能决定。因此,在接口方法中,无论返回类型,还是传入参数,涉及到的话单对象只能是抽象的。也许我们可以考虑System.Object来表示话单对象,但更好的办法是为所有的话单对象也提供一个抽象接口。

由于从目前的分析来看,抽象话单对象并没有一个公共的方法,所以这个抽象的话单接口,是一个标识接口:
public interface ICdrRecord
{
}

现在,可以对转换接口的方法进行定义了:
public interface ICdrConvert
{
 ICdrRecord Convert(string record);
 ICdrRecord Convert(ICdrRecord record);
 string Convert(ICdrRecord record);
}

自然,这样的接口定义无法通过编译。为什么呢?是因为第二个方法的签名与第三个方法的签名重复了(方法的签名和返回类型无关)。因此,我们需要为第三个方法修改名字。

但我们仔细想想,第三个方法的转换在转换接口中是必要的吗?该方法的任务是将一个话单对象转换为string类型。实际上这个责任,并不需要专门的转换对象来完成,而应属于话单对象本身的责任。再想想.Net中,所有对象均派生于System.Object,而object类型均提供了ToString()方法。

从设计的角度来看,最好的办法,是在具体的话单对象中override System.Object的ToString()方法,而不是在转换对象中,提供该转换算法。

不过考虑到,在话单处理中,更多地会调用抽象话单接口类型的对象,也许我们将ToString()方法抽象到接口ICdrRecord中会更好。
public interface ICdrRecord
{
 string ToString();
}
public interface ICdrConvert
{
 ICdrRecord Convert(string record);
 ICdrRecord Convert(ICdrRecord record);
}
而具体的话单对象,就应该实现ICdrRecord接口了。因为各话单对象均继承了System.Object,则间接地继承了object对象的ToString()方法,所以,话单对象应该重写该方法:
public class CNCCdr: ICdrRecord
{
 //网通话单格式对象的公共属性;

 //重写object的ToString()方法,同时也实现了接口ICdrRecord的ToString()方法;
 public override string ToString()
 {
  //实现具体的内容;
 }
}
public class CUCCdr: ICdrRecord
{
 //联通话单格式对象的公共属性;

 //重写object的ToString()方法,同时也实现了接口ICdrRecord的ToString()方法;
 public override string ToString()
 {
  //实现具体的内容;
 }
}
public class CMCdr: ICdrRecord
{
 //移动话单格式对象的公共属性;

 //重写object的ToString()方法,同时也实现了接口ICdrRecord的ToString()方法;
 public override string ToString()
 {
  //实现具体的内容;
 }
}

下面就是关键的实现了。由于我们已经为转换算法进行了抽象,因此根据策略模式来实现具体的转换算法,就是水到渠成的事情了。实现代码之前,先来看看UML类图:

tragedy.gif

注意看橙色部分,这一部分即为策略模式的主体。接口ICdrConvert为抽象策略角色,类CNCToCUC,CUCToCM为具体策略角色,它们分别实现了将网通话单转换为联通话单,联通话单转换为中国移动话单的算法。根据实际需要,还可以添加多个类似的具体策略角色,并实现ICdrConvert接口:
public class CNCToCUC:ICdrConvert
{
 public ICdrRecord Convert(string record)
 {
  //实现具体的转换算法;
 }
 public ICdrRecord Convert(ICdrRecord record)
 {
  //实现具体的转换算法;
 }
}
类CUCToCM的实现相似,不再重复。

那么通过策略模式实现,究竟有什么好处呢?请大家注意上图的CdrOp类。该类是抽象类,它提供了一个构造函数,可以传递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(); 
}

类CdrFileOp继承了抽象类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)方法;
    //至于实现的是何种转换,由构造函数传入的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();
}

这个类,实现了抽象类CdrOp的HandleCdr()方法。但具体的实现细节则是在私有方法Read()和Write()中完成的(根据实际情况,也可以把Read和Write方法作为公共抽象方法或保护方法,放到抽象类CdrOp中,而在抽象类CdrOp中具体提供HandleCdr方法的实现,该方法调用Read和Write方法,这样就使用了模版方法模式)。

注意看Read和Write方法中,没有一个具体类。不管是话单对象,还是话单转换对象,均是抽象接口对象。尤其是在Read()方法中,调用了_Convert的Convert方法:
cdr = _convert.Convert(_convert.Convert(line));
内部的Convert方法,即_convert.Convert(line),是将读出来的字符串转换为ICdrRecord对象,然后通过调用_convert.Convert(ICdrRecord record)方法,再将该对象转换为另一种话单格式对象,但类型仍然属于ICdrRecord。

那么在这些转换过程中,究竟转换成了哪一种话单格式对象呢?这是由_convert字段来决定的。而这个对象则是由构造函数的参数中传递进来的。

同样的道理,在Write()方法中,大家也可以看到为所有话单对象抽象为一个接口ICdrRecord的好处。通过ICdrRecord调用ToString()方法,避免了在CdrFileOp中引入具体对象。要知道,程序一旦引入具体对象,则耦合性就高了。一旦需求发生改变,就需要对编码进行修改。

有了以上的架构,客户端调用就非常方便了:
public class Client
{
 public static void Main()
 {
  string sourceFileName = “c:\CNCCdr.txt”;
  string target1= “c:\CUCCdr.txt”;
  string target2= “c:\CMCdr.txt”;
  
  //将网通话单转换为联通话单;
  ICdrOp op1 = new CdrFileOp(sourceFileName,target1,new CNCToCUC());
  op1.HandleCdr();

  //将刚才转换生成的联通话单转换为移动话单;
  ICdrOp op2 = new CdrFileOp(target1,target2,new CUCToCM());
  op2.HandleCdr();  
 }
}

当然,我们还可以引入工厂模式来创建CdrFileOp对象。或者将ICdrConvert对象设置为CdrFileOp的公共属性,而非通过构造函数来传入。然而,通过本文,策略模式的精要已经体现得淋漓尽致了。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多