坏味道——Switch声明(Switch Statements)

特征

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

问题原因

面向对象程序的一个最明显特征就是:少用 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)

收益

  • 提升代码组织性。

何时忽略

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

重构方法说明

提炼函数(Extract Method)

问题

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

  1. void printOwing() {
  2. printBanner();
  3. //print details
  4. System.out.println("name: " + name);
  5. System.out.println("amount: " + getOutstanding());
  6. }

解决

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

  1. void printOwing() {
  2. printBanner();
  3. printDetails(getOutstanding());
  4. }
  5. void printDetails(double outstanding) {
  6. System.out.println("name: " + name);
  7. System.out.println("amount: " + outstanding);
  8. }

搬移函数(Move Method)

问题

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

解决

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

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

问题

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

解决

以子类取代这个类型码。

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

问题

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

解决

以状态对象取代类型码。

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

问题

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

  1. class Bird {
  2. //...
  3. double getSpeed() {
  4. switch (type) {
  5. case EUROPEAN:
  6. return getBaseSpeed();
  7. case AFRICAN:
  8. return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  9. case NORWEGIAN_BLUE:
  10. return (isNailed) ? 0 : getBaseSpeed(voltage);
  11. }
  12. throw new RuntimeException("Should be unreachable");
  13. }
  14. }

解决

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

  1. abstract class Bird {
  2. //...
  3. abstract double getSpeed();
  4. }
  5. class European extends Bird {
  6. double getSpeed() {
  7. return getBaseSpeed();
  8. }
  9. }
  10. class African extends Bird {
  11. double getSpeed() {
  12. return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  13. }
  14. }
  15. class NorwegianBlue extends Bird {
  16. double getSpeed() {
  17. return (isNailed) ? 0 : getBaseSpeed(voltage);
  18. }
  19. }
  20. // Somewhere in client code
  21. speed = bird.getSpeed();

以明确函数取代参数(Replace Parameter with Explicit Methods)

问题

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

  1. void setValue(String name, int value) {
  2. if (name.equals("height")) {
  3. height = value;
  4. return;
  5. }
  6. if (name.equals("width")) {
  7. width = value;
  8. return;
  9. }
  10. Assert.shouldNeverReachHere();
  11. }

解决

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

  1. void setHeight(int arg) {
  2. height = arg;
  3. }
  4. void setWidth(int arg) {
  5. width = arg;
  6. }

引入 Null 对象(Introduce Null Object)

问题

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

  1. if (customer == null) {
  2. plan = BillingPlan.basic();
  3. }
  4. else {
  5. plan = customer.getPlan();
  6. }

解决

将null值替换为null对象。

  1. class NullCustomer extends Customer {
  2. Plan getPlan() {
  3. return new NullPlan();
  4. }
  5. // Some other NULL functionality.
  6. }
  7. // Replace null values with Null-object.
  8. customer = (order.customer != null) ? order.customer : new NullCustomer();
  9. // Use Null-object as if it's normal subclass.
  10. plan = customer.getPlan();

引申阅读

欢迎继续阅读 代码的症与药 系列文章。

代码的坏味道(6)——Switch声明(Switch Statements)的更多相关文章

  1. Chapter 3 :代码的坏味道

    "如果尿布臭了,就换掉它." --Beck奶奶,论保持小孩清洁的哲学 代码的坏味道这一章集中论述该何时重构.具体的重构方法在后面的章节. "没有任何度量规矩比得上见识广博 ...

  2. Bad Smell (代码的坏味道)

    sourcemaking 如果一段代码是不稳定或者有一些潜在问题的,那么代码往往会包含一些明显的痕迹.正如食物要腐坏之前,经常会发出一些异味一样, 我们管这些痕迹叫做 "代码异味" ...

  3. 【重构】 代码的坏味道总结 Bad Smell (一) (重复代码 | 过长函数 | 过大的类 | 过长参数列 | 发散式变化 | 霰弹式修改)

    膜拜下 Martin Fowler 大神 , 开始学习 圣经 重构-改善既有代码设计 . 代码的坏味道就意味着需要重构, 对代码的坏味道了然于心是重构的比要前提; . 作者 : 万境绝尘 转载请注明出 ...

  4. 重构 之 总结代码的坏味道 Bad Smell (一) 重复代码 过长函数 过大的类 过长参数列 发散式变化 霰弹式修改

    膜拜下 Martin Fowler 大神 , 开始学习 圣经 重构-改善既有代码设计 . 代码的坏味道就意味着需要重构, 对代码的坏味道了然于心是重构的比要前提; . 作者 : 万境绝尘 转载请注明出 ...

  5. Refactoring之——代码的坏味道(一)过长方法

    1 代码的坏味道 重构一书中提到了22种代码的坏味道,大致可以分为几类. 识别代码的坏味道,有助于发现代码的潜在问题,从而可以有的放矢的修改现有代码,使之不断完善. 1.1 Bloaters(臭鲱,暂 ...

  6. 消灭 Java 代码的“坏味道”

    消灭 Java 代码的“坏味道” 原创: 王超 阿里巴巴中间件 昨天 导读 明代王阳明先生在<传习录>谈为学之道时说: 私欲日生,如地上尘,一日不扫,便又有一层.着实用功,便见道无终穷,愈 ...

  7. 【转】Bad Smell(代码的坏味道)

    1.Duplicated Code(重复的代码) 臭味行列中首当其冲的就是Duplicated Code.如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好. ...

  8. 代码的坏味道(16)——纯稚的数据类(Data Class)

    坏味道--纯稚的数据类(Data Class) 特征 纯稚的数据类(Data Class) 指的是只包含字段和访问它们的getter和setter函数的类.这些仅仅是供其他类使用的数据容器.这些类不包 ...

  9. 代码的坏味道(22)——不完美的库类(Incomplete Library Class)

    坏味道--不完美的库类(Incomplete Library Class) 特征 当一个类库已经不能满足实际需要时,你就不得不改变这个库(如果这个库是只读的,那就没辙了). 问题原因 许多编程技术都建 ...

随机推荐

  1. Kooboo CMS技术文档之一:Kooboo CMS技术背景

    语言平台 依赖注入方案 存储模型 1. 语言平台 Kooboo CMS基于.NET Framework 4.x,.NET Framework 4.x的一些技术特性成为站点开发人员使用Kooboo CM ...

  2. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

  3. Spark的StandAlone模式原理和安装、Spark-on-YARN的理解

    Spark是一个内存迭代式运算框架,通过RDD来描述数据从哪里来,数据用那个算子计算,计算完的数据保存到哪里,RDD之间的依赖关系.他只是一个运算框架,和storm一样只做运算,不做存储. Spark ...

  4. CentOS:设置系统级代理(转)

    原文地址:http://www.cnblogs.com/cocowool/archive/2012/07/05/2578487.html YUM代理设置 编辑/etc/yum.conf,在最后加入 # ...

  5. Linux实战教学笔记06:Linux系统基础优化

    第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...

  6. NOIP模板整理计划

    先占个坑 [update]noip结束了,弃了 一.图论 1.单源最短路 洛谷P3371 (1)spfa 已加SLF优化 #include <iostream> #include < ...

  7. 【C#】获取网页内容及HTML解析器HtmlAgilityPack的使用

    最近经常需要下载一些东西,而这个下载地址又会经过层层跳转,每个页面上都有很多广告,烦不胜烦,所以做了一个一键获得最终下载地址的小工具.使用C#,来获取网页内容,然后通过HtmlAgilityPack获 ...

  8. Java版本:识别Json字符串并分隔成Map集合

    前言: 最近又看了点Java的知识,于是想着把CYQ.Data V5迁移到Java版本. 过程发现坑很多,理论上看大部分很相似,实践上代码写起来发现大部分都要重新思考方案. 遇到的C#转Java的一些 ...

  9. 山寨Unity3D?搜狐畅游的免费开源游戏引擎Genesis-3D

    在CSDN上看到了<搜狐畅游发布3D游戏引擎Genesis-3D 基于MIT协议开源>(http://www.csdn.net/article/2013-11-21/2817585-cha ...

  10. .Net Core 系列:1、环境搭建

    前言: 2016年6月28日微软宣布发布 .NET Core 1.0.ASP.NET Core 1.0 和 Entity Framework Core 1.0. .NET Core是微软在两年前发起的 ...