专栏首页Java程序猿部落开发中滥用面向对象,你是否违背了编程原则

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

Switch 声明

Switch 声明(Switch Statements) 你有一个复杂的 switch 语句或 if 序列语句。

问题原因

面向对象程序的一个最明显特征就是:少用 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) 将所有超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,然后让两个类都继承自它。

收益

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

重构方法说明

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

问题

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

解决

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

提炼超类(Extract Superclass)

问题

两个类有相似特性。

解决

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

本文分享自微信公众号 - Java程序猿部落(ydsy-love),作者:Java程序猿部落

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java开发中存在这样的代码,反而影响整体整洁和可读性

    许多编程技术都建立在库类的基础上。库类的作者没用未卜先知的能力,不能因此责怪他们。麻烦的是库往往构造的不够好,而且往往不可能让我们修改其中的类以满足我们的需要。

    李红
  • HashMap框架源码深入解读,面试不用愁

    在Java Collections Framework的体系中中,主要有两个重要的接口,一个是List、Set和Queue所属的Collection,还有一个就...

    李红
  • Java三大性质总结:原子性、可见性以及有序性

    1. 三大性质简介 在并发编程中分析线程安全的问题时往往需要切入点,那就是两大核心:JMM抽象内存模型以及happens-before规则,三条性质:原子性,有...

    李红
  • Tensorflow系列专题(四):神经网络篇之前馈神经网络综述

    从本章起,我们将正式开始介绍神经网络模型,以及学习如何使用TensorFlow实现深度学习算法。人工神经网络(简称神经网络)在一定程度上受到了生物学的启发,期望...

    磐创AI
  • 码如其人,同学你能写一手漂亮的Python函数吗

    在机器学习中,我们经常需要使用类和函数定义模型的各个部分,例如定义读取数据的函数、预处理数据的函数、模型架构和训练过程的函数等等。那么什么样的函数才是漂亮的、赏...

    用户2769421
  • (1)James Stewart Calculus 5th Edition:Functions and Models

    dodo_lihao
  • 3分钟读懂C语言函数:这些例子一看就懂!|一键删除账户教学

    最近发现,有些小伙伴对C语言的函数有些难以理解,其实呢,C语言的函数很好理解,只不过部分人在学习的时候,没有找到好的例子来类比理解而已。这篇文章会教大家如何去理...

    诸葛青云
  • 码如其人,同学你能写一手漂亮的Python函数吗

    与多数现代编程语言一样,在 Python 中,函数是抽象和封装的基本方法之一。你在开发阶段或许已经写过数百个函数,但并非每个函数都生而平等。写出「糟糕的」函数会...

    机器之心
  • 什么是python的函数体

    函数的概念,函数是将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集

    砸漏
  • 7.1 函数

    1、函数是从英文function翻译过来的,其实function在英文中的意思既是函数,也是功能。、

    C语言入门到精通

扫码关注云+社区

领取腾讯云代金券