设计中的微创手术――使用重构原则消灭重复代码

    与演习的作用一样,当我们认知了“重构”这个词之后,在设计时会时刻警惕法则所提到的各种味道,一旦嗅到一些坏的味道,那么就意味着我们要做一些改变了。

当然,如果你并不知道这个可能神秘的词汇,那么也没关系,下面的情况我想大家都遇到过。

我们有一个读取文件的函数readFile(string filepath),这个函数一直运行的很好。但是突然在某一天,我们要加入“读取特定格式的文件”这个新的功能,但是当我们跟往常一样编写代码的时候却发现,新的功能实现代码中,有许多曾经实现过的。

瞬间,肾上腺激素告诉我们:“不,这是不允许的,因为我很懒,同样的代码休想让我写两遍。”但是,前人关于“CPP会让你的代码极难维护”的经验会让我们的手做出这样的决定:为旧函数增加参数使之适应新的需求。

那么我们把它修改成了readFile(string filepath, EMFileType filetype),嗯,对于新的代码我很满意。但是编译器似乎并不认同我的观点――旧的客户代码并没有调用新的函数,编译出错。也许我们应该马上修改编译器指示的那行红色的代码,不不等等,我们真的要改吗?同样的旧代码可能在很多很多的地方出现,如果我们确实有毅力改掉,那么增加第三个功能的时候还要重复?重复这两个字可以完全击垮我的意志,算了,还是不要提它的好。我们并没有到悬崖边上。

嗯,我想大家都想到了这个办法,哈哈,对了,就是保留旧的函数框架,但函数体内去调用新的函数,就像这样,当然优秀的编译器还为我们带来了好用的函数重载:

readFile(string filepath)

{

       readfile(filepath, ftAll); //ftAll是默认的文件类型

}

这样我们没有见到那个可恶的重复,而编译器也非常满意的完成了任务。

 

到这里故事貌似并没有结束,是的,除了函数,我们在进行OOD的时候,遇到同样的问题也是家常便饭。

我们已经实现了一个书籍的类型Book,包含了标题、页数、定价等的字段,当然还包含了送货信息。老板对这个网上售书系统很满意,当然也归功于这个没有出问题的书籍类。

但是,某一天,问题终于来了,老板要扩展业务,增加一个电子书的销售功能,但是,旧的纸质版书籍类型并没有提供下载、文件大小、文件格式等信息,看来我们要再增加一个类型了,嗯,就叫它电子书类。而就在同时,我们也发现,旧的书籍类型有很多东西都可以拿来用,复用是个多么让人激动的词汇啊。这个时候,我想大家至少会想出两种办法来搞定:

1、 使用泛化。

我们首先想到了泛化,这是个很正常的反映,当然也很简单。

同时创建PaperBookEBook两个类型来分别代表传统书籍和电子书,把EBook类型不能被复用的成员放到PagerBook类型中,然后再在EBook类型中增加它特有的成员。最后让这两个类型继承自Book这个书籍的基类(由原来的书籍类型转换而来)。见下图:

Book

PagerBook

EBook

之后,老板告诉我们,很满意。

2、 使用组合。

当然,我们可能还有另一种方法,也不难。

把书籍类型的所有可复用成员提取出来,建立一个BookBaseInfo的类型,之后新增EBook类型。最后在BookEBook两个类型中增加一个BookBaseInfo类型的组合成员。

 

BookBaseInfo

PagerBook

EBook

 

嗯,似乎老板也很满意。

 

故事应该还没有结束,因为导演的烟刚抽一半,那么我们继续吧。

任务完成后应该是总结,对于一个职业病级的程序员来说,摔两次跟头会想到第三、第四次是最基本的技能,那么我们来看上面两种方法的优缺点。

首先是泛化方式。使用泛化,可以方便的使用多态来进行父子转换操作,符合了接口隔离原则。但是似乎除了C++之外,其它语言都不支持多重继承,如果将来这些子类还需要重构其它的成员而产生新的父类,那我们的日子就不大好过了。当然我们想到了接口,接口可以多重继承啊,但是,另一个问题是,接口需要实现类型自己来实现代码,这样又会出现可怕的重复

然后说说组合方式。使用组合,可以很容易的避免多重继承带来的限制,我们可以任意组合多个类型的成员,但这样做的问题是,我们无法隐藏实现细节,需要把组合的成员暴露给客户代码。

如果说上面的总结只是“完美主义”在作怪,但是,事情似乎来的更快。又是某一天,老板又提出了他的新需求:我们要提供各种书的配套源码下载。这是好事啊(某找不到XX书配套源码的仁兄感激涕零的Or2),可对于技术并不是什么让人兴奋的事情――重构再次降临,而且非常幸运,我们遇到了刚刚还在批判的“完美主义”。看来,我们不得不找一个更好的办法了。

既然泛化和组合方式各有优缺点,那我们是不是可以哈,也许在描述泛化方式的时候你已经想到了(所以我会及时的收笔,免得你不会再往下看了,呵呵):

使用接口和组合方式相结合的方法,当然稍微有些复杂。

定义一个IBookBaseInfo接口,然后定义一个类型BookBaseInfo,之后我们延续上述的组合方式,然后,我们分别在BookEBook两个类型中实现IBookBaseInfo接口,实现部分来调用组合的BookBaseInfo类型成员。

 

 

EBook

IBookBaseInfo

这样形成一个代理模式之后,就可以让客户代码专心的使用它所需要的逻辑了。

 

OK,导演的烟屁股开始着火了,我们也圆满完成任务。

标签:
文章分类 FK Coding, 一张窗户纸

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

点赞
如果您觉得很赞,我将非常乐意接受虚拟币的捐赠,以示您对我的肯定。

比特币钱包地址:
1PqpqA8FyH3NbfCrbcRd1YxQk3LEsSEYDV
莱特币钱包地址:
LRTdmovGGVEHCKWz7JdL9aiB7VZkuNycJf
站点勋章
网站统计