代码坏味道之滥用面向对象

栏目: 后端 · 发布时间: 5年前

内容简介:面向对象程序的一个最明显特征就是:少用大多数时候,一看到

面向对象程序的一个最明显特征就是:少用 switchcase 语句。从本质上说, 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)

收益

  • 提升代码组织性。

代码坏味道之滥用面向对象

何时忽略

  • 如果一个 switch 操作只是执行简单的行为,就没有重构的必要了。
  • switch 常被工厂 设计模式 族( 工厂方法模式(Factory Method)抽象工厂模式(Abstract Factory) )所使用,这种情况下也没必要重构。

重构方法说明

提炼函数(Extract Method)

问题

你有一段代码可以组织在一起。

void printOwing() {
  printBanner();

  //print details
  System.out.println("name: " + name);
  System.out.println("amount: " + getOutstanding());
}
复制代码

解决

移动这段代码到一个新的函数中,使用函数的调用来替代老代码。

void printOwing() {
  printBanner();
  printDetails(getOutstanding());
}

void printDetails(double outstanding) {
  System.out.println("name: " + name);
  System.out.println("amount: " + outstanding);
}
复制代码

搬移函数(Move Method)

问题

你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。

代码坏味道之滥用面向对象

解决

在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。

代码坏味道之滥用面向对象

以子类取代类型码(Replace Type Code with Subclass)

问题

你有一个不可变的类型码,它会影响类的行为。

代码坏味道之滥用面向对象

解决

以子类取代这个类型码。

代码坏味道之滥用面向对象

以状态/策略模式取代类型码(Replace Type Code with State/Strategy)

问题

你有一个类型码,它会影响类的行为,但你无法通过继承消除它。

代码坏味道之滥用面向对象

解决

以状态对象取代类型码。

代码坏味道之滥用面向对象

以多态取代条件表达式(Replace Conditional with Polymorphism)

问题

你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。

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");
  }
}
复制代码

解决

将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。

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)

问题

你有一个函数,其中完全取决于参数值而采取不同的行为。

void setValue(String name, int value) {
  if (name.equals("height")) {
    height = value;
    return;
  }
  if (name.equals("width")) {
    width = value;
    return;
  }
  Assert.shouldNeverReachHere();
}
复制代码

解决

针对该参数的每一个可能值,建立一个独立函数。

void setHeight(int arg) {
  height = arg;
}
void setWidth(int arg) {
  width = arg;
}
复制代码

引入 Null 对象(Introduce Null Object)

问题

你需要再三检查某对象是否为 null。

if (customer == null) {
  plan = BillingPlan.basic();
}
else {
  plan = customer.getPlan();
}
复制代码

解决

将 null 值替换为 null 对象。

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)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。

代码坏味道之滥用面向对象

问题原因

有时你会看到这样的对象:其内某个实例变量仅为某种特定情况而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初设置目的,会让你发疯。 通常,临时字段是在某一算法需要大量输入时而创建。因此,为了避免函数有过多参数,程序员决定在类中创建这些数据的临时字段。这些临时字段仅仅在算法中使用,其他时候却毫无用处。 这种代码不好理解。你期望查看对象字段的数据,但是出于某种原因,它们总是为空。

解决方法

  • 可以通过 提炼类(Extract Class) 将临时字段和操作它们的所有代码提炼到一个单独的类中。此外,你可以运用 以函数对象取代函数(Replace Method with Method Object) 来实现同样的目的。
  • 引入 Null 对象(Introduce Null Object) 在“变量不合法”的情况下创建一个 null 对象,从而避免写出条件表达式。

代码坏味道之滥用面向对象

收益

  • 更好的代码清晰度和组织性。

代码坏味道之滥用面向对象

重构方法说明

提炼类(Extract Class)

问题

某个类做了不止一件事。

代码坏味道之滥用面向对象

解决

建立一个新类,将相关的字段和函数从旧类搬移到新类。

代码坏味道之滥用面向对象

以函数对象取代函数(Replace Method with Method Object)

问题

你有一个过长函数,它的局部变量交织在一起,以致于你无法应用提炼函数(Extract Method) 。

class Order {
  //...
  public double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}
复制代码

解决

将函数移到一个独立的类中,使得局部变量成了这个类的字段。然后,你可以将函数分割成这个类中的多个函数。

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。

if (customer == null) {
  plan = BillingPlan.basic();
}
else {
  plan = customer.getPlan();
}
复制代码

解决

将 null 值替换为 null 对象。

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)

两个类中有着不同的函数,却在做着同一件事。

代码坏味道之滥用面向对象

问题原因

这种情况往往是因为:创建这个类的 程序员 并不知道已经有实现这个功能的类存在了。

解决方法

  • 如果两个函数做同一件事,却有着不同的签名,请运用 函数改名(Rename Method) 根据它们的用途重新命名。
  • 运用 搬移函数(Move Method)添加参数(Add Parameter)令函数携带参数(Parameterize Method) 来使得方法的名称和实现一致。
  • 如果两个类仅有部分功能是重复的,尝试运用 提炼超类(Extract Superclass) 。这种情况下,已存在的类就成了超类。
  • 当最终选择并运用某种方法来重构后,也许你就能删除其中一个类了。

收益

  • 消除了不必要的重复代码,为代码瘦身了。
  • 代码更易读(不再需要猜测为什么要有两个功能相同的类)。

代码坏味道之滥用面向对象

何时忽略

  • 有时合并类是不可能的,或者是如此困难以至于没有意义。例如:两个功能相似的类存在于不同的 lib 库中。

重构方法说明

函数改名(Rename Method)

问题

函数的名称未能恰当的揭示函数的用途。

class Person {
  public String getsnm();
}
复制代码

解决

修改函数名。

class Person {
  public String getSecondName();
}
复制代码

搬移函数(Move Method)

问题

你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。

代码坏味道之滥用面向对象

解决

在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。

代码坏味道之滥用面向对象

添加参数(Add Parameter)

问题某个函数需要从调用端得到更多信息。

class Customer {
  public Contact getContact();
}
复制代码

解决为此函数添加一个对象函数,让改对象带进函数所需信息。

class Customer {
  public Contact getContact(Date date);
}
复制代码

令函数携带参数(Parameterize Method)

问题

若干函数做了类似的工作,但在函数本体中却包含了不同的值。

代码坏味道之滥用面向对象

解决

建立单一函数,以参数表达哪些不同的值。

代码坏味道之滥用面向对象

提炼超类(Extract Superclass)

问题

两个类有相似特性。

代码坏味道之滥用面向对象

解决

为这两个类建立一个超类,将相同特性移至超类。

代码坏味道之滥用面向对象

被拒绝的馈赠

被拒绝的馈赠(Refused Bequest)

子类仅仅使用父类中的部分方法和属性。其他来自父类的馈赠成为了累赘。

代码坏味道之滥用面向对象

问题原因

有些人仅仅是想重用超类中的部分代码而创建了子类。但实际上超类和子类完全不同。

解决方法

以委托取代继承(Replace Inheritance with Delegation)
提炼超类(Extract Superclass)

代码坏味道之滥用面向对象


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Getting Started with C++ Audio Programming for Game Development

Getting Started with C++ Audio Programming for Game Development

David Gouveia

Written specifically to help C++ developers add audio to their games from scratch, this book gives a clear introduction to the concepts and practical application of audio programming using the FMOD li......一起来看看 《Getting Started with C++ Audio Programming for Game Development》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具