`
zhmocean
  • 浏览: 195634 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

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

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

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

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

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

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

readFile(string filepath)

{

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

}

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

<!----><o:p> </o:p>

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

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

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

1、 使用泛化。<o:p></o:p>

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

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

<!----><v:group coordsize="7200,2310" id="_x0000_s1051" coordorigin="2944,10917" editas="canvas" style="WIDTH: 414pt; HEIGHT: 132.6pt; mso-position-horizontal-relative: char; mso-position-vertical-relative: line"><o:lock v:ext="edit" aspectratio="t"></o:lock><v:shapetype o:spt="75" coordsize="21600,21600" filled="f" stroked="f" id="_x0000_t75" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:extrusionok="f" o:connecttype="rect" gradientshapeok="t"></v:path><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape id="_x0000_s1052" type="#_x0000_t75" o:preferrelative="f" style="LEFT: 2944px; WIDTH: 7200px; POSITION: absolute; TOP: 10917px; HEIGHT: 2310px"><v:fill o:detectmouseclick="t"></v:fill><v:path o:extrusionok="t" o:connecttype="none"></v:path><o:lock text="t" v:ext="edit"></o:lock></v:shape><v:rect id="_x0000_s1053" style="LEFT: 5761px; WIDTH: 1566px; POSITION: absolute; TOP: 10917px; HEIGHT: 408px"><v:textbox>

Book

</v:textbox></v:rect><v:rect id="_x0000_s1054" style="LEFT: 4823px; WIDTH: 1565px; POSITION: absolute; TOP: 12276px; HEIGHT: 407px"><v:textbox>

PagerBook

</v:textbox></v:rect><v:rect id="_x0000_s1055" style="LEFT: 6857px; WIDTH: 1566px; POSITION: absolute; TOP: 12276px; HEIGHT: 407px"><v:textbox>

EBook

</v:textbox></v:rect><v:rect fillcolor="silver" id="_x0000_s1056" style="LEFT: 5761px; WIDTH: 1566px; POSITION: absolute; TOP: 11325px; HEIGHT: 271px"></v:rect><v:rect fillcolor="silver" id="_x0000_s1057" style="LEFT: 4822px; WIDTH: 1565px; POSITION: absolute; TOP: 12683px; HEIGHT: 271px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:rect fillcolor="silver" id="_x0000_s1058" style="LEFT: 6857px; WIDTH: 1564px; POSITION: absolute; TOP: 12683px; HEIGHT: 272px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:shapetype o:oned="t" o:spt="32" coordsize="21600,21600" filled="f" id="_x0000_t32" path="m,l21600,21600e"><v:path fillok="f" o:connecttype="none" arrowok="t"></v:path><o:lock shapetype="t" v:ext="edit"></o:lock></v:shapetype><v:shape id="_x0000_s1059" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 5606px; WIDTH: 938px; POSITION: absolute; TOP: 11596px; HEIGHT: 680px; flip: y"><v:stroke endarrowwidth="wide" endarrow="block"></v:stroke></v:shape><v:shape id="_x0000_s1060" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 6544px; WIDTH: 1097px; POSITION: absolute; TOP: 11596px; HEIGHT: 680px; flip: x y"><v:stroke endarrowwidth="wide" endarrow="block"></v:stroke></v:shape><w:wrap type="none"></w:wrap><w:anchorlock></w:anchorlock></v:group>

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

2、 使用组合。<o:p></o:p>

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

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

 

<v:group coordsize="7200,2310" id="_x0000_s1041" coordorigin="2944,10917" editas="canvas" style="WIDTH: 414pt; HEIGHT: 132.6pt; mso-position-horizontal-relative: char; mso-position-vertical-relative: line"><o:lock v:ext="edit" aspectratio="t"></o:lock><v:shape id="_x0000_s1042" type="#_x0000_t75" o:preferrelative="f" style="LEFT: 2944px; WIDTH: 7200px; POSITION: absolute; TOP: 10917px; HEIGHT: 2310px"><v:fill o:detectmouseclick="t"></v:fill><v:path o:extrusionok="t" o:connecttype="none"></v:path><o:lock text="t" v:ext="edit"></o:lock></v:shape><v:rect id="_x0000_s1043" style="LEFT: 5918px; WIDTH: 1565px; POSITION: absolute; TOP: 12412px; HEIGHT: 407px"><v:textbox>

BookBaseInfo

</v:textbox></v:rect><v:rect id="_x0000_s1044" style="LEFT: 4666px; WIDTH: 1564px; POSITION: absolute; TOP: 11053px; HEIGHT: 408px"><v:textbox>

PagerBook

</v:textbox></v:rect><v:rect id="_x0000_s1045" style="LEFT: 7169px; WIDTH: 1566px; POSITION: absolute; TOP: 11053px; HEIGHT: 408px"><v:textbox>

EBook

</v:textbox></v:rect><v:rect fillcolor="silver" id="_x0000_s1046" style="LEFT: 5918px; WIDTH: 1565px; POSITION: absolute; TOP: 12819px; HEIGHT: 273px"></v:rect><v:rect fillcolor="silver" id="_x0000_s1047" style="LEFT: 4665px; WIDTH: 1564px; POSITION: absolute; TOP: 11461px; HEIGHT: 270px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:rect fillcolor="silver" id="_x0000_s1048" style="LEFT: 7169px; WIDTH: 1565px; POSITION: absolute; TOP: 11461px; HEIGHT: 271px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:shape id="_x0000_s1049" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 6701px; WIDTH: 1251px; POSITION: absolute; TOP: 11732px; HEIGHT: 680px; flip: x"><v:stroke endarrowlength="long" endarrowwidth="wide" endarrow="diamond"></v:stroke></v:shape><v:shape id="_x0000_s1050" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 5447px; WIDTH: 1254px; POSITION: absolute; TOP: 11731px; HEIGHT: 681px"><v:stroke endarrowlength="long" endarrowwidth="wide" endarrow="diamond"></v:stroke></v:shape><w:wrap type="none"></w:wrap><w:anchorlock></w:anchorlock></v:group>

<o:p> </o:p>

嗯,似乎老板也很满意。

<o:p> </o:p>

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

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

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

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

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

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

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

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

 

 

<v:group coordsize="7200,3533" id="_x0000_s1026" coordorigin="2944,9694" editas="canvas" style="WIDTH: 414pt; HEIGHT: 202.8pt; mso-position-horizontal-relative: char; mso-position-vertical-relative: line"><o:lock v:ext="edit" aspectratio="t"></o:lock><v:rect id="_x0000_s1030" style="LEFT: 7169px; WIDTH: 1566px; POSITION: absolute; TOP: 11053px; HEIGHT: 408px"><v:textbox>

EBook

</v:textbox></v:rect><v:rect fillcolor="silver" id="_x0000_s1031" style="LEFT: 5918px; WIDTH: 1565px; POSITION: absolute; TOP: 12819px; HEIGHT: 273px"></v:rect><v:rect fillcolor="silver" id="_x0000_s1032" style="LEFT: 4665px; WIDTH: 1564px; POSITION: absolute; TOP: 11461px; HEIGHT: 270px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:rect fillcolor="silver" id="_x0000_s1033" style="LEFT: 7169px; WIDTH: 1565px; POSITION: absolute; TOP: 11461px; HEIGHT: 271px"><v:fill src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" o:title="宽上对角线" type="pattern"></v:fill></v:rect><v:shape id="_x0000_s1034" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 6701px; WIDTH: 1251px; POSITION: absolute; TOP: 11732px; HEIGHT: 680px; flip: x"><v:stroke endarrowlength="long" endarrowwidth="wide" endarrow="diamond"></v:stroke></v:shape><v:shapetype o:spt="10" coordsize="21600,21600" id="_x0000_t10" adj="6326" path="m@0,l0@0,0@2@0,21600@1,21600,21600@2,21600@0@1,xe"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="val #0"></v:f><v:f eqn="sum width 0 #0"></v:f><v:f eqn="sum height 0 #0"></v:f><v:f eqn="prod @0 2929 10000"></v:f><v:f eqn="sum width 0 @3"></v:f><v:f eqn="sum height 0 @3"></v:f><v:f eqn="val width"></v:f><v:f eqn="val height"></v:f><v:f eqn="prod width 1 2"></v:f><v:f eqn="prod height 1 2"></v:f></v:formulas><v:path limo="10800,10800" o:connecttype="custom" textboxrect="0,0,21600,21600;2700,2700,18900,18900;5400,5400,16200,16200" gradientshapeok="t" o:connectlocs="@8,0;0,@9;@8,@7;@6,@9"></v:path><v:handles><v:h position="#0,topLeft" switch="" xrange="0,10800"></v:h></v:handles></v:shapetype><v:shape id="_x0000_s1036" type="#_x0000_t10" style="LEFT: 6231px; WIDTH: 156px; POSITION: absolute; TOP: 10509px; HEIGHT: 156px"></v:shape><v:shape id="_x0000_s1037" type="#_x0000_t10" style="LEFT: 7170px; WIDTH: 158px; POSITION: absolute; TOP: 10509px; HEIGHT: 156px"></v:shape><v:shape id="_x0000_s1038" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 5448px; WIDTH: 861px; POSITION: absolute; TOP: 10665px; HEIGHT: 388px; flip: y"><v:stroke dashstyle="dash" endarrow="block"></v:stroke></v:shape><v:shape id="_x0000_s1039" type="#_x0000_t32" o:connectortype="straight" style="LEFT: 7249px; WIDTH: 704px; POSITION: absolute; TOP: 10665px; HEIGHT: 388px; flip: x y"><v:stroke dashstyle="dash" endarrow="block"></v:stroke></v:shape><v:rect id="_x0000_s1040" style="LEFT: 5918px; WIDTH: 1722px; POSITION: absolute; TOP: 9966px; HEIGHT: 407px"><v:textbox>

IBookBaseInfo

</v:textbox></v:rect><w:wrap type="none"></w:wrap><w:anchorlock></w:anchorlock></v:group>

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

<o:p> </o:p>

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics