--- title: 代码坏味道之滥用面向对象 date: 2018-10-13 22:48:00 order: 03 categories: - 设计 - 重构 tags: - 设计 - 重构 permalink: /pages/3ba0474c/ --- > 翻译自:https://sourcemaking.com/refactoring/smells/oo-abusers > > **滥用面向对象(Object-Orientation Abusers)这组坏味道意味着:代码部分或完全地违背了面向对象编程原则。** ## Switch 声明 > Switch 声明(Switch Statements) > > 你有一个复杂的 `switch` 语句或 `if` 序列语句。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/switch-statements-1.png) ### 问题原因 面向对象程序的一个最明显特征就是:少用 `switch` 和 `case` 语句。从本质上说,`switch` 语句的问题在于重复(`if` 序列也同样如此)。你常会发现 `switch` 语句散布于不同地点。如果要为它添加一个新的 `case` 子句,就必须找到所有 `switch` 语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。 大多数时候,一看到 `switch` 语句,就应该考虑以多态来替换它。 ### 解决方法 - 问题是多态该出现在哪?switch 语句常常根据类型码进行选择,你要的是“与该类型码相关的函数或类”,所以应该运用 `提炼函数(Extract Method)` 将 `switch` 语句提炼到一个独立函数中,再以 `搬移函数(Move Method)` 将它搬移到需要多态性的那个类里。 - 如果你的 `switch` 是基于类型码来识别分支,这时可以运用 `以子类取代类型码(Replace Type Code with Subclass)` 或 `以状态/策略模式取代类型码(Replace Type Code with State/Strategy)` 。 - 一旦完成这样的继承结构后,就可以运用 `以多态取代条件表达式(Replace Conditional with Polymorphism)` 了。 - 如果条件分支并不多并且它们使用不同参数调用相同的函数,多态就没必要了。在这种情况下,你可以运用 `以明确函数取代参数(Replace Parameter with Explicit Methods)` 。 - 如果你的选择条件之一是 null,可以运用 `引入 Null 对象(Introduce Null Object)` 。 ### 收益 - 提升代码组织性。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/switch-statements-2.png) ### 何时忽略 - 如果一个 `switch` 操作只是执行简单的行为,就没有重构的必要了。 - `switch` 常被工厂设计模式族(`工厂方法模式(Factory Method)`和`抽象工厂模式(Abstract Factory)`)所使用,这种情况下也没必要重构。 ### 重构方法说明 #### 提炼函数(Extract Method) **问题** 你有一段代码可以组织在一起。 ```java void printOwing() { printBanner(); //print details System.out.println("name: " + name); System.out.println("amount: " + getOutstanding()); } ``` **解决** 移动这段代码到一个新的函数中,使用函数的调用来替代老代码。 ```java void printOwing() { printBanner(); printDetails(getOutstanding()); } void printDetails(double outstanding) { System.out.println("name: " + name); System.out.println("amount: " + outstanding); } ``` #### 搬移函数(Move Method) **问题** 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 以子类取代类型码(Replace Type Code with Subclass) **问题** 你有一个不可变的类型码,它会影响类的行为。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-before.png) **解决** 以子类取代这个类型码。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-after.png) #### 以状态/策略模式取代类型码(Replace Type Code with State/Strategy) **问题** 你有一个类型码,它会影响类的行为,但你无法通过继承消除它。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-before.png) **解决** 以状态对象取代类型码。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-after.png) #### 以多态取代条件表达式(Replace Conditional with Polymorphism) **问题** 你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。 ```java class Bird { //... double getSpeed() { switch (type) { case EUROPEAN: return getBaseSpeed(); case AFRICAN: return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; case NORWEGIAN_BLUE: return (isNailed) ? 0 : getBaseSpeed(voltage); } throw new RuntimeException("Should be unreachable"); } } ``` **解决** 将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。 ```java abstract class Bird { //... abstract double getSpeed(); } class European extends Bird { double getSpeed() { return getBaseSpeed(); } } class African extends Bird { double getSpeed() { return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; } } class NorwegianBlue extends Bird { double getSpeed() { return (isNailed) ? 0 : getBaseSpeed(voltage); } } // Somewhere in client code speed = bird.getSpeed(); ``` #### 以明确函数取代参数(Replace Parameter with Explicit Methods) **问题** 你有一个函数,其中完全取决于参数值而采取不同的行为。 ```java void setValue(String name, int value) { if (name.equals("height")) { height = value; return; } if (name.equals("width")) { width = value; return; } Assert.shouldNeverReachHere(); } ``` **解决** 针对该参数的每一个可能值,建立一个独立函数。 ```java void setHeight(int arg) { height = arg; } void setWidth(int arg) { width = arg; } ``` #### 引入 Null 对象(Introduce Null Object) **问题** 你需要再三检查某对象是否为 null。 ```java if (customer == null) { plan = BillingPlan.basic(); } else { plan = customer.getPlan(); } ``` **解决** 将 null 值替换为 null 对象。 ```java class NullCustomer extends Customer { Plan getPlan() { return new NullPlan(); } // Some other NULL functionality. } // Replace null values with Null-object. customer = (order.customer != null) ? order.customer : new NullCustomer(); // Use Null-object as if it's normal subclass. plan = customer.getPlan(); ``` ## 临时字段 > 临时字段(Temporary Field)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-1.png) ### 问题原因 有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。 通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处。 这种代码不好理解。你期望查看对象字段的数据,但是出于某种原因,它们总是为空。 ### 解决方法 - 可以通过 `提炼类(Extract Class)` 将临时字段和操作它们的所有代码提炼到一个单独的类中。此外,你可以运用 `以函数对象取代函数(Replace Method with Method Object)` 来实现同样的目的。 - `引入 Null 对象(Introduce Null Object)` 在“变量不合法”的情况下创建一个 null 对象,从而避免写出条件表达式。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-2.png) ### 收益 - 更好的代码清晰度和组织性。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-3.png) ### 重构方法说明 #### 提炼类(Extract Class) **问题** 某个类做了不止一件事。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 以函数对象取代函数(Replace Method with Method Object) **问题** 你有一个过长函数,它的局部变量交织在一起,以致于你无法应用提炼函数(Extract Method) 。 ```java class Order { //... public double price() { double primaryBasePrice; double secondaryBasePrice; double tertiaryBasePrice; // long computation. //... } } ``` **解决** 将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。 ```java class Order { //... public double price() { return new PriceCalculator(this).compute(); } } class PriceCalculator { private double primaryBasePrice; private double secondaryBasePrice; private double tertiaryBasePrice; public PriceCalculator(Order order) { // copy relevant information from order object. //... } public double compute() { // long computation. //... } } ``` #### 引入 Null 对象(Introduce Null Object) **问题** 你需要再三检查某对象是否为 null。 ```java if (customer == null) { plan = BillingPlan.basic(); } else { plan = customer.getPlan(); } ``` **解决** 将 null 值替换为 null 对象。 ```java class NullCustomer extends Customer { Plan getPlan() { return new NullPlan(); } // Some other NULL functionality. } // Replace null values with Null-object. customer = (order.customer != null) ? order.customer : new NullCustomer(); // Use Null-object as if it's normal subclass. plan = customer.getPlan(); ``` ## 异曲同工的类 > 异曲同工的类(Alternative Classes with Different Interfaces) > > 两个类中有着不同的函数,却在做着同一件事。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/alternative-classes-with-different-interfaces-1.png) ### 问题原因 这种情况往往是因为:创建这个类的程序员并不知道已经有实现这个功能的类存在了。 ### 解决方法 - 如果两个函数做同一件事,却有着不同的签名,请运用 `函数改名(Rename Method)` 根据它们的用途重新命名。 - 运用 `搬移函数(Move Method)` 、 `添加参数(Add Parameter)` 和 `令函数携带参数(Parameterize Method)` 来使得方法的名称和实现一致。 - 如果两个类仅有部分功能是重复的,尝试运用 `提炼超类(Extract Superclass)` 。这种情况下,已存在的类就成了超类。 - 当最终选择并运用某种方法来重构后,也许你就能删除其中一个类了。 ### 收益 - 消除了不必要的重复代码,为代码瘦身了。 - 代码更易读(不再需要猜测为什么要有两个功能相同的类)。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/alternative-classes-with-different-interfaces-2.png) ### 何时忽略 - 有时合并类是不可能的,或者是如此困难以至于没有意义。例如:两个功能相似的类存在于不同的 lib 库中。 ### 重构方法说明 #### 函数改名(Rename Method) **问题** 函数的名称未能恰当的揭示函数的用途。 ```java class Person { public String getsnm(); } ``` **解决** 修改函数名。 ```java class Person { public String getSecondName(); } ``` #### 搬移函数(Move Method) **问题** 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 添加参数(Add Parameter) **问题** 某个函数需要从调用端得到更多信息。 ```java class Customer { public Contact getContact(); } ``` **解决** 为此函数添加一个对象函数,让改对象带进函数所需信息。 ``` class Customer { public Contact getContact(Date date); } ``` #### 令函数携带参数(Parameterize Method) **问题** 若干函数做了类似的工作,但在函数本体中却包含了不同的值。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/parameterize-method-before.png) **解决** 建立单一函数,以参数表达哪些不同的值。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/parameterize-method-after.png) #### 提炼超类(Extract Superclass) **问题** 两个类有相似特性。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-before.png) **解决** 为这两个类建立一个超类,将相同特性移至超类。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-after.png) ## 被拒绝的馈赠 > 被拒绝的馈赠(Refused Bequest) > > 子类仅仅使用父类中的部分方法和属性。其他来自父类的馈赠成为了累赘。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-1.png) ### 问题原因 有些人仅仅是想重用超类中的部分代码而创建了子类。但实际上超类和子类完全不同。 ### 解决方法 - 如果继承没有意义并且子类和父类之间确实没有共同点,可以运用 `以委托取代继承(Replace Inheritance with Delegation)` 消除继承。 - 如果继承是适当的,则去除子类中不需要的字段和方法。运用 `提炼超类(Extract Superclass)` 将所有超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,然后让两个类都继承自它。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-2.png) ### 收益 - 提高代码的清晰度和组织性。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-3.png) ### 重构方法说明 #### 以委托取代继承(Replace Inheritance with Delegation) **问题** 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-inheritance-with-delegation-before.png) **解决** 1. 在子类中新建一个字段用以保存超类; 2. 调整子类函数,令它改而委托超类; 3. 然后去掉两者之间的继承关系。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-inheritance-with-delegation-after.png) #### 提炼超类(Extract Superclass) **问题** 两个类有相似特性。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-before.png) **解决** 为这两个类建立一个超类,将相同特性移至超类。 ![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-after.png) ## 扩展阅读 - [代码的坏味道和重构](https://github.com/dunwu/design/blob/master/docs/refactor/代码的坏味道和重构.md) - [代码坏味道之代码臃肿](https://github.com/dunwu/design/blob/master/docs/refactor/代码坏味道之代码臃肿.md) - [代码坏味道之滥用面向对象](https://github.com/dunwu/design/blob/master/docs/refactor/代码坏味道之滥用面向对象.md) - [代码坏味道之变革的障碍](https://github.com/dunwu/design/blob/master/docs/refactor/代码坏味道之变革的障碍.md) - [代码坏味道之非必要的](https://github.com/dunwu/design/blob/master/docs/refactor/代码坏味道之非必要的.md) - [代码坏味道之耦合](https://github.com/dunwu/design/blob/master/docs/refactor/代码坏味道之耦合.md) ## 参考资料 - [《代码整洁之道》](https://book.douban.com/subject/4199741/) - [《重构 - 改善既有代码的设计》](https://book.douban.com/subject/4262627/) - [《代码大全》](https://book.douban.com/subject/1477390/) - https://sourcemaking.com/refactoring