我的工作需要写一个话单转换工具,在写这个工具的过程中,发现整个实现恰恰可以说是策略模式最好的体现。也许用这个例子来说明策略模式的应用,最是合适。该话单转换工具的目的,是将某个服务提供商的话单文本文件,转换为另一个服务提供商的话单文本文件。如将联通的话单格式转换为移动的话单格式。而话单转换工具的要求,是希望能实现多个服务提供商话单文本文件的互相转换。
我们来分析一下任务。首先,各种服务提供商的话单格式,无疑是不相同的。例如,话单采集后,字段的顺序,字段的宽度以及字段间的分隔符,都不相同。但是,从总体上来说,话单的表现形式是大致相同的,这为我们实现话单转换提供了一个技术上可行的前提。
所谓话单转换,就是需要将一个话单文本文件读出,然后对每一行的字符串进行识别后,再转换为符合相对应的服务提供商标准的话单文本文件。操作很简单,就是文本文件的读写而已,不同的就在于转换的方法。根据服务提供商标准的不同,我们应该为每一种转换提供相对应的算法。而所谓策略模式,正是对算法的包装和抽象,将算法的责任和其本身分离。所以,我们现在要做的工作就是将转换话单的算法抽象出来。
根据工具的要求,话单转换应该包括3个方法:1、将文件读出的一行字符串转换为对应的话单对象;2、将一种话单对象转换为另一种话单对象;3、将话单对象转换为字符串,以方便写入话单文本文件;
根据以上的分析,我们还需要为不同的话单格式建立相应的对象。例如:网通、联通和移动的话单格式对象分别为:
接下来就应该实现话单转换的算法了。首先需要将算法进行抽象,而进行抽象的最佳选择莫过于使用接口,例如我们定义一个用于话单转换的接口ICdrConvert:public interface ICdrConvert{}按照如前的分析,在接口中应该包括三个转换方法。但是现在有个问题,就是转换的话单对象。由于方法在接口中,为一个抽象。而话单对象可能有多种,具体转换为何种话单对象,需要到具体实现时才能决定。因此,在接口方法中,无论返回类型,还是传入参数,涉及到的话单对象只能是抽象的。也许我们可以考虑System.Object来表示话单对象,但更好的办法是为所有的话单对象也提供一个抽象接口。
由于从目前的分析来看,抽象话单对象并没有一个公共的方法,所以这个抽象的话单接口,是一个标识接口:public interface ICdrRecord{}
现在,可以对转换接口的方法进行定义了:
自然,这样的接口定义无法通过编译。为什么呢?是因为第二个方法的签名与第三个方法的签名重复了(方法的签名和返回类型无关)。因此,我们需要为第三个方法修改名字。
但我们仔细想想,第三个方法的转换在转换接口中是必要的吗?该方法的任务是将一个话单对象转换为string类型。实际上这个责任,并不需要专门的转换对象来完成,而应属于话单对象本身的责任。再想想.Net中,所有对象均派生于System.Object,而object类型均提供了ToString()方法。
从设计的角度来看,最好的办法,是在具体的话单对象中override System.Object的ToString()方法,而不是在转换对象中,提供该转换算法。
不过考虑到,在话单处理中,更多地会调用抽象话单接口类型的对象,也许我们将ToString()方法抽象到接口ICdrRecord中会更好。
而具体的话单对象,就应该实现ICdrRecord接口了。因为各话单对象均继承了System.Object,则间接地继承了object对象的ToString()方法,所以,话单对象应该重写该方法:
下面就是关键的实现了。由于我们已经为转换算法进行了抽象,因此根据策略模式来实现具体的转换算法,就是水到渠成的事情了。实现代码之前,先来看看UML类图:
注意看橙色部分,这一部分即为策略模式的主体。接口ICdrConvert为抽象策略角色,类CNCToCUC,CUCToCM为具体策略角色,它们分别实现了将网通话单转换为联通话单,联通话单转换为中国移动话单的算法。根据实际需要,还可以添加多个类似的具体策略角色,并实现ICdrConvert接口:
类CUCToCM的实现相似,不再重复。那么通过策略模式实现,究竟有什么好处呢?请大家注意上图的CdrOp类。该类是抽象类,它提供了一个构造函数,可以传递ICdrConvert对象:
类CdrFileOp继承了抽象类CdrOp:
这个类,实现了抽象类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中引入具体对象。要知道,程序一旦引入具体对象,则耦合性就高了。一旦需求发生改变,就需要对编码进行修改。
有了以上的架构,客户端调用就非常方便了:
当然,我们还可以引入工厂模式来创建CdrFileOp对象。或者将ICdrConvert对象设置为CdrFileOp的公共属性,而非通过构造函数来传入。
通过本文的讲解,你会发现策略模式并没有什么神秘的,无非还是一种对象的抽象,唯一不同的是,策略模式是对算法的抽象而已。所以,“抽象才是硬道理”,当我们在作面向对象设计时,需时时刻刻记住这句话啊!
posted on 2005-02-23 19:45 张逸 阅读(4947) 评论(18) 编辑 收藏 网摘 所属分类: Design & Pattern
好文章!看了一遍,有点难.先收着! 回复 引用
学而不思则罔,思而不学则殆. Nice! 回复 引用 查看
好久没有看到你写的文章了:)不错!解决了关键的转换问题。不过我还是想谈谈我的想法: 1)目前只有联通、网通、移动三家公司,所以转换策略仅有两个。一旦公司数增加到五家或更多,策略就会急剧增长。是否可以考虑设立一个内部“格式”,其它格式都向这个格式看齐,以控制变化。 2)cdr = _convert.Convert(_convert.Convert(line));这条命令让我思考了半天。后来才反映过来此_convert.Convert与彼_convert.Convert不是一个Convert,应用了多态在里面。但并没有很好的体现“代码就是最好的注释”,因为这两个Convert的含义孑然不同。后来我才注意到,策略的命名是CNCToCUC,隐含的规定了数据的来源与数据的去处。所以_convert.Convert(line)是将源转换为“公共格式”(我是这么理解的),而后一个_convert.Convert是将公共格式转换为目标格式,两个Convert虽然名字相同意义确完全不同。是否可以考虑隔离接口? 我想,是否可以设计多个Reader与Writer,然后通过依赖注入来实现动态组合。Reader的功能是将一个格式转换成“公共”格式,而Writer是将公共格式转换成目标格式。其实XML、Memory Stream、File等就有很多的Reader与Writer相互转换(更象装饰模式)。 回复 引用 查看
谢谢吕震宇! 第二个建议很好,我在分析对象责任的时候,看来出现了错误。确实应该将这两个不同的Convert方法分离开。不过方法public ICdrRecord Convert(string record),并非是将其转换为公共格式,而是将一个字符串转换为具体的话单对象。因此,这个方法应该放到具体的话单对象中。 你说的第一个建议固然是好,可惜从我要实现的业务来看,不太可能。因为网通、联通和移动的话单不仅格式不一样,某些字段也不相同。如网通话单中,有些字段在联通中根本就没有。因此,要建立公共格式,至少需要一个标准才行,相对来说,这是比较困难的。 不过,可以以这个例子做个假设,写出更完美的方案来。 回复 引用 查看
呵呵,好!其实只有针对具体的问题,模式才可能发挥作用,毕竟设计模式的目的也为的是更好的解决问题,而不是花架子。一切从实际出发才行。 我对实际情况不太了解,所以才提出了“公共格式”,现在看来没有什么必要了。 回复 引用 查看
本期博客园期刊使用了您的这篇文章,如果有异议,请发邮件到William_Fire@126.com 回复 引用
如果就这个例子的话,用Templet Method模式应该也可以吧,把整体算法都写在抽象基类里作为模版,而将热点Read();和Write();方法做成virtual的在具体的子类里override 不过聚合优先于继承,策略模式应该更加灵活些,至少策略类还可以重用 回复 引用
@pppp: (根据实际情况,也可以把Read和Write方法作为公共抽象方法或保护方法,放到抽象类CdrOp中,而在抽象类CdrOp中具体提供HandleCdr方法的实现,该方法调用Read和Write方法,这样就使用了模版方法模式)。 这一点我已在文中说明。本文的关键不在于Read和Write方法,而是构造函数中传递的参数ICdrConvert对象,这才是策略模式的应用。 回复 引用 查看
现在总算是能看明白这种程度的文章了! 在过一段时间就能给出不同的意见和看法了! 高兴一小会儿! 回复 引用
看到中间才反应过来不是java,汗.... UML图中好像CdrOp和ICdrconvert那根线应该反过来把,还少了个箭头:) 回复 引用
几个转换类实在是没有必要,如果需要转换的格式增多,想想吧,要实现得类太多了。确立一个中间格式虽然困难但一定可行。你说的“如网通话单中,有些字段在联通中根本就没有”,那你怎么转换他们呢~既然这个可以办到,那么就可以和中间格式互相转化,所以对于这篇文章模式虽然应用的好,但设计应该多多优化。 还有一个就是我实在看不出来CdrOp和CdrFileOp两个类到底怎么回事,还请指教 回复 引用
CdrOp与CdrFileOp 是为了抽象数据源的读取吗? 回复 引用
CdrFileOp是CdrOp的功能实现,所以从现实意义上来看,CdrFileOp是一组功能的集合,为什么CdrOp不以接口的方式出现而是使用抽象类呢? 回复 引用
@kevin_supersun如果不考虑单继承的约束,最佳实践是优先使用抽象类而不是接口。 回复 引用 查看
我觉得这个接口 public interface ICdrConvert { ICdrRecord Convert(string record); ICdrRecord Convert(ICdrRecord record); } 里面的两个方法为什么不合并一下? 不觉得有太大的必要区分开来,直接一个ICdrRecord Convert(string record)就可以了, 这样也不会有后面这段_convert.Convert(_convert.Convert(line))让人觉得有点奇怪的代码。 回复 引用 查看
有点晦涩 回复 引用 查看
没看懂..... 回复 引用 查看
昵称: [登录] [注册]
主页:
邮箱:(仅博主可见)
验证码: 看不清,换一个
评论内容:
登录 注册
[使用Ctrl+Enter键快速提交评论]
《软件设计精要与模式》
《WCF服务编程》