开发中滥用面向对象,你是否违背了编程原则

Switch 声明

Switch 声明(Switch Statements)git

你有一个复杂的 switch 语句或 if 序列语句。程序员

问题缘由

面向对象程序的一个最明显特征就是:少用 switch 和 case 语句。从本质上说,switch 语句的问题在于重复(if 序列也一样如此)。你常会发现 switch 语句散布于不一样地点。若是要为它添加一个新的 case 子句,就必须找到全部 switch语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。github

大多数时候,一看到 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());
}

解决this

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

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

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

搬移函数(Move Method)

问题设计

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

解决

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

以子类取代类型码(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) 将全部超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,而后让两个类都继承自它。

收益

  • 提升代码的清晰度和组织性。

重构方法说明

以委托取代继承(Replace Inheritance with Delegation)

问题

某个子类只使用超类接口中的一部分,或是根本不须要继承而来的数据。

解决

  1. 在子类中新建一个字段用以保存超类;
  2. 调整子类函数,令它改而委托超类;
  3. 而后去掉二者之间的继承关系。

 

提炼超类(Extract Superclass)

问题

两个类有类似特性。

 

解决

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

相关文章
相关标签/搜索