重构-改善既有代码的设计:处理概括关系 (九)
2014-11-16 19:06
387 查看
简化函数调用
两个子类拥有相同的字段。将该字段移至超类。
![](http://my.csdn.net/uploads/201206/05/1338894114_1840.jpg)
如果各子类是分别开发的,或者是在重构过程中组合起来的,你常会发现它们拥有重复特性,特别是字段更容易重复。这样的字段有时拥有相似的名字,但也并非绝对如此。判断若干字段是否重复,唯一的办法就是观察函数如何使用它们。如果它们被使用的方式很相似,你就可以将它们归纳到超类去。
有些函数,在各个子类中产生完全相同的结果。将该函数移至超类。
![](http://my.csdn.net/uploads/201206/05/1338894420_7526.jpg)
避免重复行为是很重要的。尽管重复的2个函数也可以各自工作的很好,但重复自身只会成为错误的滋生地,此外别无价值。无论何时,只要系统内出现重复,你就面临“修改其中一个却未能修改另一个”的风险。通常,找出重复也有一定困难。
如果某个函数在各子类中的函数体相同,这就是做显而易见的Pull Up Method (方法上移)适用场合。当然,情况并不总是如此明显。你也可以只管放心重构,再看看测试程序会不会发牢骚,但这就需要对你的测试有充分的信心。观察这些可能重复的函数之间的差异往往大有收获:它们经常会展示那些忘记测试的行为。
Pull Up Method (方法上移)常常紧随其他重构而被使用。也许你能找出若干个身处不同子类的函数,而它们又可以通过某种形式的参数调整成为相同的函数。这时候,最简单的办法就是首先分别调整这些函数的参数,然后再将它们概况到超类中。当然,如果你足够自信,也可以一次完成这2个步骤。
有一种特殊情况也需要使用Pull Up Method (方法上移):子类的函数覆写了超类的函数,但仍然做相同的工作。
Pull Up Method (方法上移)过程中最麻烦的一点就是:被提升的函数可能会引用只出现于子类而不出现于超类的特性。如果被引用的是个函数,你可以将该函数也一同提升到超类,或者在超类中建立一个抽象函数。在此过程中,你可能需要修改某个函数的签名,或建立一个委托函数。
如果2个函数相似但不相同,你或许可以先借助Form Template Method (塑造模板函数)构造出相同的函数,然后再提升它们
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。在超类中新建一个构造函数,并在子类构造函数中调用它。
![](http://my.csdn.net/uploads/201206/05/1338894491_5736.jpg)
构造函数是很奇妙的东西。它们不是普通函数,使用它们比使用普通函数受到更多的限制。
如果你看见各个子类中的函数有共同的行为,第一个念头应该是将共同行为提炼到一个独立函数中,然后将这个函数提升到超类。对构造函数而言,它们彼此的共同行为往往就是“对象的构建”。这时候你需要在超类中提供一个构造函数,然后让子类都来调用它。很多时候,子类构造函数的唯一动作就是调用超类构造函数。这里不能运用 Pull Up Method
(方法上移),因为你无法在子类中继承超类构造函数。
如果重构过程过于复杂,你可以考虑使用 Replace Constructor with Factory Method (以工厂函数取代构造函数)。
超类中的某个函数只与部分子类有关。将这个函数移到相关的那些子类去。
![](http://my.csdn.net/uploads/201206/05/1338894554_3325.jpg)
push down Method (函数下移)和Pull Up Method (函数上移)恰恰相反,当有必要把某些行为从超类移至特定的子类时,就使用push down Method (函数下移),它通常也只在这种时候使用。使用Extract Subclass (提炼子类)之后可能会需要它.
超类中的某个字段只被部分子类用到,将这个字段移到需要它的那些子类去。
![](http://my.csdn.net/uploads/201206/05/1338894640_9732.jpg)
如果只有某些子类需要超类内的一个字段,那就可以使用本项重构。
类中的某些特性只被某些实例用到。新建一个子类,将上面所说的那一部分特性移到子类中。
![](http://my.csdn.net/uploads/201206/05/1338894730_6772.jpg)
使用Extract Subclass (提炼子类)的主要动机是:你发现类中的某些行为只被一部分实例用到,其他实例不需要它们。有时候这种行为上的差异是通过类型码区分的,此时你可以使用Replace Type Code with Subclass (以子类取代类型码)或Replace Type Code with State/Strategy (以状态策略取代类型码)。但是,并非一定要出现了类型码才表示需要考虑使用子类。
Extract Class (提炼类)是Extract Subclass (提炼子类)之外的另一个选择,2者之间的抉择其实就是委托和继承之间的抉择。Extract Subclass (提炼子类)通常更容易进行,但它也有限制:一旦对象创建完成你无法再改变与类型的相关行为,但如果使用Extract Class (提炼类),你只需插入另一个组件就可以改变对象的行为。此外,子类只能用以表现一组变化。如果你希望一个类以几种不同的方式变化,就必须使用委托。
两个类有相似特性。为这2个类建立一个超类,将相同特性移至超类。
![](http://my.csdn.net/uploads/201206/05/1338895172_1478.jpg)
重复代码是系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。
重复代码的某种形式就是:2个类以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承。但是,在建立这些具有共通性的类之前,你往往无法发现这样的共通性,因此常常会在具有共通性的类出现之后,再开始建立其间的继承结构。
另一种选择就是 Extract Class(提炼类)。这2种方案之间的选择其实就是继承和委托之间的选择。如果2个类可以共享行为,也可以共享接口,那么继承是比较简单的做法。如果你选错了,也总有 Replace Inheritance with Delegation (以委托取代继承)这瓶后悔药可吃。
若干客户使用类接口中的同一子集,或者2个类的接口部分相同。将相同的子集提炼到一个独立接口中。
![](http://my.csdn.net/uploads/201206/05/1338895223_7283.jpg)
类之间彼此互用的方式有若干种。“使用一个类”通常意味着用到该类的所有责任区。另一种情况是,某一组客户只使用类责任区中的一个特定子集。再一种情况是,这个类需要与所有协助处理某些特定请求的类合作。
对于后2种情况,将真正用到的这部分责任分离出来通常很有意义,因为这样可以使系统的用法更清晰,同时也更容易看清系统的责任划分。如果新的类需要支持上述子集,也比较能够看清子集内有些什么东西。
在许多面向对象语言中,这种责任划分是通过多继承来实现的。在c#中可以运用接口来诏示并实现上述需求。
Extract Subclass (提炼子类)和Extract Interface (提炼接口)之间有些相似之处。Extract Interface (提炼接口)只能提炼共通接口,不能提炼共通代码。使用Extract Interface (提炼接口)可能造成难闻的“重复”坏味道,幸而你可以运用Extract Class(提炼类)先把共通行为放进一个组件中,然后将工作委托给该组件,从而解决这个问题。如果有不少共通行为,Extract
Superclass (提炼超类)会比较简单,但是每个类只能有一个超类。
如果某个类在不同环境下扮演截然不同的角色,使用接口就是个好主意。你可以针对每个角色以Extract Interface (提炼接口)提炼出相应接口。另一种可以用Extract Interface (提炼接口)的情况是:你想要描述一个类的外部依赖接口。如果你打算将来加入其它种类的服务对象。只需要求它们实现这个接口即可。
超类和子类之间无太大区别。将它们和为一体。
![](http://my.csdn.net/uploads/201206/05/1338895256_1570.jpg)
如果你曾经编写过继承体系,就会知道,继承体系很容易变得过分复杂。所谓重构继承体系,往往是将函数和字段在体系中上下移到。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把超类和子类合并起来。
你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节不同。将这些操作分别放进独立的函数中,并保持它们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至超类。
![](http://my.csdn.net/uploads/201206/05/1338895311_1936.jpg)
继承是避免重复行为的一个强大工具。无论何时,只要你看见2个子类之中有类似的函数,就可以把它们提升到超类。但是如果这些函数并不完全相同该这么办?仍有必要尽量避免重复,但又必须保持这些函数之间的实质差异。
常见的一种情况是:2个函数以相同的顺序执行大致相近的操作,但是各操作不完全相同。这种情况下我们可以将执行的序列移至超类,并借助多态保证各操作仍得以保持差异性。这样的函数被称为Template Method(模板函数)。
某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉2者之间的继承关系。
![](http://my.csdn.net/uploads/201206/05/1338895356_7341.jpg)
继承是个好东西,但有时候它并不是你要的。你常常会遇到这样的情况:一开始继承了一个类,随后发现超类中的许多操作并不真正适用于子类。这种情况下,你所拥有的接口并未真正反映出子类的功能。或者,你可能发现你从超类中继承了一大堆子类并不需要的数据,抑或你可能发现超类中的某些protected函数对子类并没有什么意义。
你可以选择容忍,并接受传统说法:子类可以只使用超类功能的一部分。但这样的结果是:代码传达的信息与你的意图南辕北辙,你应该将它去除。
如果以委托取代继承,你可以更清晰地表明:你只需要受委托的一部分功能。接口中的哪一部分应该被使用,哪一部分应该被忽略,完全由你主导控制。这样做的成本则是需要额外写出委托函数,但这些函数都给出简单,极少可能出错。
你在2个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。让委托类继承受托类。
![](http://my.csdn.net/uploads/201206/05/1338895396_4821.jpg)
本项重构与Replace Inheritance with Delegation (以委托取代继承)恰恰相反。如果你发现自己需要受托类中的所有函数,并且花费很大力气编写所有极简单的委托函数,本重构可以帮助你轻松回头使用继承。
2条告诫需牢记与心:首先,如果你并没有使用受托类的所有函数,那么就不应该使用Replace Delegation with Inheritance (以继承取代委托),因为子类应该总是遵循超类的接口。如果过多的委托函数让你烦心,你有别的选择:你可以通过 Remove Middle Man (移除中间人)让客户端自己调用受托函数,也可以使用Extract
Superclass (提炼超类)将2个类接口相同的部分提炼到超类中,然后让2个类都继承这个新的超类;你还可以用类似手法使用Extract Superclass (提炼超类)。
另一种需要当心的情况是:受托对象被不止一个其他对象共享,而且受托对象是可变的。在这种情况下,你就不能将委托关系替换为继承关系,因为这样就无法再共享数据了。数据共享是必须由受托对象承担的一种责任,你无法把它转给继承关系。如果受托对象是不可变的,数据共享就不成问题,因为你大可放心地复制对象。
1. Pull Up Field 字段上移
两个子类拥有相同的字段。将该字段移至超类。![](http://my.csdn.net/uploads/201206/05/1338894114_1840.jpg)
如果各子类是分别开发的,或者是在重构过程中组合起来的,你常会发现它们拥有重复特性,特别是字段更容易重复。这样的字段有时拥有相似的名字,但也并非绝对如此。判断若干字段是否重复,唯一的办法就是观察函数如何使用它们。如果它们被使用的方式很相似,你就可以将它们归纳到超类去。
2. Pull up Method 函数上移
有些函数,在各个子类中产生完全相同的结果。将该函数移至超类。![](http://my.csdn.net/uploads/201206/05/1338894420_7526.jpg)
避免重复行为是很重要的。尽管重复的2个函数也可以各自工作的很好,但重复自身只会成为错误的滋生地,此外别无价值。无论何时,只要系统内出现重复,你就面临“修改其中一个却未能修改另一个”的风险。通常,找出重复也有一定困难。
如果某个函数在各子类中的函数体相同,这就是做显而易见的Pull Up Method (方法上移)适用场合。当然,情况并不总是如此明显。你也可以只管放心重构,再看看测试程序会不会发牢骚,但这就需要对你的测试有充分的信心。观察这些可能重复的函数之间的差异往往大有收获:它们经常会展示那些忘记测试的行为。
Pull Up Method (方法上移)常常紧随其他重构而被使用。也许你能找出若干个身处不同子类的函数,而它们又可以通过某种形式的参数调整成为相同的函数。这时候,最简单的办法就是首先分别调整这些函数的参数,然后再将它们概况到超类中。当然,如果你足够自信,也可以一次完成这2个步骤。
有一种特殊情况也需要使用Pull Up Method (方法上移):子类的函数覆写了超类的函数,但仍然做相同的工作。
Pull Up Method (方法上移)过程中最麻烦的一点就是:被提升的函数可能会引用只出现于子类而不出现于超类的特性。如果被引用的是个函数,你可以将该函数也一同提升到超类,或者在超类中建立一个抽象函数。在此过程中,你可能需要修改某个函数的签名,或建立一个委托函数。
如果2个函数相似但不相同,你或许可以先借助Form Template Method (塑造模板函数)构造出相同的函数,然后再提升它们
3. Pull up Constructor Body 构造函数本体上移
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。在超类中新建一个构造函数,并在子类构造函数中调用它。![](http://my.csdn.net/uploads/201206/05/1338894491_5736.jpg)
构造函数是很奇妙的东西。它们不是普通函数,使用它们比使用普通函数受到更多的限制。
如果你看见各个子类中的函数有共同的行为,第一个念头应该是将共同行为提炼到一个独立函数中,然后将这个函数提升到超类。对构造函数而言,它们彼此的共同行为往往就是“对象的构建”。这时候你需要在超类中提供一个构造函数,然后让子类都来调用它。很多时候,子类构造函数的唯一动作就是调用超类构造函数。这里不能运用 Pull Up Method
(方法上移),因为你无法在子类中继承超类构造函数。
如果重构过程过于复杂,你可以考虑使用 Replace Constructor with Factory Method (以工厂函数取代构造函数)。
4. Push down Method 函数下移
超类中的某个函数只与部分子类有关。将这个函数移到相关的那些子类去。![](http://my.csdn.net/uploads/201206/05/1338894554_3325.jpg)
push down Method (函数下移)和Pull Up Method (函数上移)恰恰相反,当有必要把某些行为从超类移至特定的子类时,就使用push down Method (函数下移),它通常也只在这种时候使用。使用Extract Subclass (提炼子类)之后可能会需要它.
5. Push down Fiedld 字段下移
超类中的某个字段只被部分子类用到,将这个字段移到需要它的那些子类去。![](http://my.csdn.net/uploads/201206/05/1338894640_9732.jpg)
如果只有某些子类需要超类内的一个字段,那就可以使用本项重构。
6. Extract Subclass 提炼子类
类中的某些特性只被某些实例用到。新建一个子类,将上面所说的那一部分特性移到子类中。![](http://my.csdn.net/uploads/201206/05/1338894730_6772.jpg)
使用Extract Subclass (提炼子类)的主要动机是:你发现类中的某些行为只被一部分实例用到,其他实例不需要它们。有时候这种行为上的差异是通过类型码区分的,此时你可以使用Replace Type Code with Subclass (以子类取代类型码)或Replace Type Code with State/Strategy (以状态策略取代类型码)。但是,并非一定要出现了类型码才表示需要考虑使用子类。
Extract Class (提炼类)是Extract Subclass (提炼子类)之外的另一个选择,2者之间的抉择其实就是委托和继承之间的抉择。Extract Subclass (提炼子类)通常更容易进行,但它也有限制:一旦对象创建完成你无法再改变与类型的相关行为,但如果使用Extract Class (提炼类),你只需插入另一个组件就可以改变对象的行为。此外,子类只能用以表现一组变化。如果你希望一个类以几种不同的方式变化,就必须使用委托。
7. Extract Superclass 提炼超类
两个类有相似特性。为这2个类建立一个超类,将相同特性移至超类。![](http://my.csdn.net/uploads/201206/05/1338895172_1478.jpg)
重复代码是系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。
重复代码的某种形式就是:2个类以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承。但是,在建立这些具有共通性的类之前,你往往无法发现这样的共通性,因此常常会在具有共通性的类出现之后,再开始建立其间的继承结构。
另一种选择就是 Extract Class(提炼类)。这2种方案之间的选择其实就是继承和委托之间的选择。如果2个类可以共享行为,也可以共享接口,那么继承是比较简单的做法。如果你选错了,也总有 Replace Inheritance with Delegation (以委托取代继承)这瓶后悔药可吃。
8. Extract Interface 提炼接口
若干客户使用类接口中的同一子集,或者2个类的接口部分相同。将相同的子集提炼到一个独立接口中。![](http://my.csdn.net/uploads/201206/05/1338895223_7283.jpg)
类之间彼此互用的方式有若干种。“使用一个类”通常意味着用到该类的所有责任区。另一种情况是,某一组客户只使用类责任区中的一个特定子集。再一种情况是,这个类需要与所有协助处理某些特定请求的类合作。
对于后2种情况,将真正用到的这部分责任分离出来通常很有意义,因为这样可以使系统的用法更清晰,同时也更容易看清系统的责任划分。如果新的类需要支持上述子集,也比较能够看清子集内有些什么东西。
在许多面向对象语言中,这种责任划分是通过多继承来实现的。在c#中可以运用接口来诏示并实现上述需求。
Extract Subclass (提炼子类)和Extract Interface (提炼接口)之间有些相似之处。Extract Interface (提炼接口)只能提炼共通接口,不能提炼共通代码。使用Extract Interface (提炼接口)可能造成难闻的“重复”坏味道,幸而你可以运用Extract Class(提炼类)先把共通行为放进一个组件中,然后将工作委托给该组件,从而解决这个问题。如果有不少共通行为,Extract
Superclass (提炼超类)会比较简单,但是每个类只能有一个超类。
如果某个类在不同环境下扮演截然不同的角色,使用接口就是个好主意。你可以针对每个角色以Extract Interface (提炼接口)提炼出相应接口。另一种可以用Extract Interface (提炼接口)的情况是:你想要描述一个类的外部依赖接口。如果你打算将来加入其它种类的服务对象。只需要求它们实现这个接口即可。
9. Collapse Hierarchy 折叠继承体系
超类和子类之间无太大区别。将它们和为一体。![](http://my.csdn.net/uploads/201206/05/1338895256_1570.jpg)
如果你曾经编写过继承体系,就会知道,继承体系很容易变得过分复杂。所谓重构继承体系,往往是将函数和字段在体系中上下移到。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把超类和子类合并起来。
10. From TemPlate Method 塑造模板函数
你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节不同。将这些操作分别放进独立的函数中,并保持它们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至超类。![](http://my.csdn.net/uploads/201206/05/1338895311_1936.jpg)
继承是避免重复行为的一个强大工具。无论何时,只要你看见2个子类之中有类似的函数,就可以把它们提升到超类。但是如果这些函数并不完全相同该这么办?仍有必要尽量避免重复,但又必须保持这些函数之间的实质差异。
常见的一种情况是:2个函数以相同的顺序执行大致相近的操作,但是各操作不完全相同。这种情况下我们可以将执行的序列移至超类,并借助多态保证各操作仍得以保持差异性。这样的函数被称为Template Method(模板函数)。
11. Replace Inheritance with delegation 以委托取代继承
某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉2者之间的继承关系。![](http://my.csdn.net/uploads/201206/05/1338895356_7341.jpg)
继承是个好东西,但有时候它并不是你要的。你常常会遇到这样的情况:一开始继承了一个类,随后发现超类中的许多操作并不真正适用于子类。这种情况下,你所拥有的接口并未真正反映出子类的功能。或者,你可能发现你从超类中继承了一大堆子类并不需要的数据,抑或你可能发现超类中的某些protected函数对子类并没有什么意义。
你可以选择容忍,并接受传统说法:子类可以只使用超类功能的一部分。但这样的结果是:代码传达的信息与你的意图南辕北辙,你应该将它去除。
如果以委托取代继承,你可以更清晰地表明:你只需要受委托的一部分功能。接口中的哪一部分应该被使用,哪一部分应该被忽略,完全由你主导控制。这样做的成本则是需要额外写出委托函数,但这些函数都给出简单,极少可能出错。
12. Replace delegation with Inheritance 以继承代替委托
你在2个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。让委托类继承受托类。![](http://my.csdn.net/uploads/201206/05/1338895396_4821.jpg)
本项重构与Replace Inheritance with Delegation (以委托取代继承)恰恰相反。如果你发现自己需要受托类中的所有函数,并且花费很大力气编写所有极简单的委托函数,本重构可以帮助你轻松回头使用继承。
2条告诫需牢记与心:首先,如果你并没有使用受托类的所有函数,那么就不应该使用Replace Delegation with Inheritance (以继承取代委托),因为子类应该总是遵循超类的接口。如果过多的委托函数让你烦心,你有别的选择:你可以通过 Remove Middle Man (移除中间人)让客户端自己调用受托函数,也可以使用Extract
Superclass (提炼超类)将2个类接口相同的部分提炼到超类中,然后让2个类都继承这个新的超类;你还可以用类似手法使用Extract Superclass (提炼超类)。
另一种需要当心的情况是:受托对象被不止一个其他对象共享,而且受托对象是可变的。在这种情况下,你就不能将委托关系替换为继承关系,因为这样就无法再共享数据了。数据共享是必须由受托对象承担的一种责任,你无法把它转给继承关系。如果受托对象是不可变的,数据共享就不成问题,因为你大可放心地复制对象。
相关文章推荐
- 重构改善既有代码的设计-处理概括关系
- 处理概括关系(读书摘要——重构改善既有代码的设计)
- 《重构--改善既有代码的设计》--处理概括关系(11)
- 重构-改善既有代码的设计:处理概括关系 (九)
- 重构改善既有代码的设计-处理概括关系
- 重构-改善既有代码的设计:处理概括关系 (九)
- 改善代码设计 —— 处理概括关系(Dealing w“.NET技术”ith Generalization)
- 改善代码设计 —— 处理概括关系(Dealing with Generalization)
- 一起谈.NET技术,改善代码设计 —— 处理概括关系(Dealing with Generalization)
- 改善代码设计 —— 处理概括关系(Dealing with Generalization)
- 改善代码设计 —— 处理概括关系(Dealing with Generalization)
- 改善代码设计 “.NET研究”—— 处理概括关系(Dealing with Generalization)
- 《重构-改善既有代码的设计》概括
- 代码重构一(处理概括关系)
- 重构-改善既有代码的设计总结
- 重构 — 改善既有的类图设计 条款8:消除继承类和基类中重复的依赖关系
- 重构 改善既有代码的设计:代码的坏味道
- 学习《重构-改善既有代码的设计》一
- 《重构-改善既有代码的设计》 读书心得
- 读《重构_改善既有的代码设计》有感