繁体   English   中英

重构 switch 语句 - 使用对象与原始数据类型

[英]Refactor switch statement - using an object vs primitive data type

我正在阅读《重构:改进现有代码的设计》(第 1 版)一书,学习如何重构 Java 代码。 作者进行了如下所示的重构,并解释了他为什么这样做,我不清楚。 有人可以帮助我理解解释吗? 该代码用于电影租赁软件。

public class Movie {

    //Lets call these constants "priceCode".
    public static final int  CHILDRENS = 2;
    public static final int  REGULAR = 0;
    public static final int  NEW_RELEASE = 1;

//...more code here...
}

class Rental {
    private Movie _movie;
    private int _daysRented;
//...more code here...
}

重构前:

class Rental...
   double getCharge() {
       double result = 0;
       switch (getMovie().getPriceCode()) {
           case Movie.REGULAR:
               result += 2;
               if (getDaysRented() > 2)
                   result += (getDaysRented() - 2) * 1.5;
               break;
           case Movie.NEW_RELEASE:
               result += getDaysRented() * 3;
               break;
           case Movie.CHILDRENS:
               result += 1.5;
               if (getDaysRented() > 3)
                   result += (getDaysRented() - 3) * 1.5;
               break;
       }
       return result;
  }

我们将上面的方法代码移到已经存在的 Movie 类中。

重构后:

 class Movie...
   double getCharge(int daysRented) {
       double result = 0;
       switch (getPriceCode()) {
           case Movie.REGULAR:
               result += 2;
               if (daysRented > 2)
                   result += (daysRented - 2) * 1.5;
               break;
           case Movie.NEW_RELEASE:
               result += daysRented * 3;
               break;
           case Movie.CHILDRENS:
               result += 1.5;
               if (daysRented > 3)
                   result += (daysRented - 3) * 1.5;
               break;
       }
       return result;
   }

作者的解释:

根据另一个对象的属性进行切换是一个坏主意。 如果您必须使用 switch 语句,它应该使用您自己的数据,而不是其他人的数据。

为此,为了工作,我必须传递租金的长度,这当然是租金的数据。 该方法有效地使用了两条数据,租赁的长度和电影的类型。 为什么我更喜欢将租借时长传递给电影而不是电影类型传递给租借? 这是因为提议的更​​改都是关于添加新类型的。 类型信息通常更易变。 如果我改变电影类型,我想要最小的涟漪效应,所以我更喜欢计算电影内的费用。

我的问题:

为什么根据另一个对象的属性进行切换是个坏主意?

这些事情的真正含义是“类型信息通常是易变的”和“最小的涟漪效应(已实现)”? 这些模糊的词(volatile 和ripple)并没有清楚地解释这种重构的优势。

PS - 请注意,这不是最终代码。 本书后面有更多的重构。 我知道这是一本 1990 年代的过时书。 尽管如此,这本书似乎有很多关于设计代码的想法和概念,这些想法和概念在今天仍然有用。 但是,我不确定这本书在 2020 年会有多大帮助。而且,这本书的最新版本重构:改进现有代码的设计(第 2 版)使用了我不知道的 Javascript。

为什么根据另一个对象的属性进行切换是个坏主意?

不是。

总的来说,作者似乎指的是长期维护的代码库中可能发生的变化,以及修改该代码库的难度。 至少可以想象它们可能是正确的,但这不是软件开发的任何一般或普遍接受的原则。

为便于阅读,答案已分为两个答案。 这是第 1 部分。


解决“作者的解释”:

根据另一个对象的属性进行切换是一个坏主意。 如果您必须使用 switch 语句,它应该使用您自己的数据,而不是其他人的数据。

我对这个声明感到惊讶,因为MovieRental类的私有变量。 因此,切换并不是真正发生在另一个对象的属性上。 它发生在私有对象的属性上。

作者重构的代码也违反了单一职责原则。 Movie只负责电影相关信息,不加载租借信息。 因此, getCharge()方法似乎在Rental类中的正确位置。


... 为什么我更喜欢将租借时长传递给电影而不是电影类型传递给租借? 这是因为提议的更​​改都是关于添加新类型的。

好像又不对了。 根据我的经验,即使在非电影环境中Type也是非常稳定的常量。 例如:汽车的类型非常稳定——“Car.Sedan”、“Car.SUV”等。


类型信息通常更易变。 如果我改变电影类型,我想要最小的涟漪效应,所以我更喜欢计算电影内的费用。

为了本地化类型的易变性,特定类型应该在使用它的每个类之外成为一个枚举。 示例:在MovieRental之外创建MovieType枚举。 现在每部电影都有一个私有变量,它告诉当前电影是什么类型(或 priceCode 是什么),并且租借对象将能够从其_movie私有变量的 getter 访问该“类型”。

无论何时添加类型,都必须解决两件事:

  1. 在代码中的某处创建新类型的表示;
  2. 添加这种新类型自带的业务逻辑。

无论您的实施如何,您都必须同时解决这两个问题。 因此,纹波或多或少是相同的。 唯一会改变的是你做出这些改变的地方。 当类具有单一职责时,更自然地确定应该在哪里进行更改。

为什么根据另一个对象的属性进行切换是个坏主意?

因为如果该属性的类型发生更改,您必须在自己的代码中进行更改。 如果Movie.priceCode类型从int切换为enum那么您还必须更改Rental代码。 但是您将价格计算移动到Movie不是更改Movie.priceCode类型而不影响Rental

这些事情的真正含义是“类型信息通常是易变的”和“最小的涟漪效应(已实现)”? 这些模糊的词(volatile 和ripple)并没有清楚地解释这种重构的优势。

如果您决定为电影(如 HBO 系列、迪士尼系列等)添加更多价格代码,则您必须在MovieRental类中进行更改。 但是,如果您不在Rental使用Movie.priceCode ,那么即使您从电影中删除priceCode或重命名或更改其类型,您也根本不需要更改Rental类。 在这种情况下,重构有助于本地化更改的范围。 假设您为Movie编写代码,另一个团队为Rental编写代码,并且您决定添加更多价格类型。 现在,您需要通知其他团队您添加了更多类型,为它们提供值、计算逻辑,并可能使用新版本的Movie类构建新工件。 但是,如果您只为它们提供getCharge(daysRented)方法,那么您可以添加任何新类型而无需通知开销。

为便于阅读,答案已分为两个答案。 这是第 2 部分。


解决您的问题:

为什么根据另一个对象的属性进行切换是个坏主意?

仅仅因为,另一个对象,是“另一个”对象。 您无法知道该对象内部发生了什么。 因此,如果这些方法依赖于内部变量,它们会更加稳定。

例如:如果MoviegetPriceCode()行为不同,如果Movie的价格代码在这些调用之间发生变化,那么RentalgetCharge()方法将返回不同的值。

但是在这种场景下不适用,因为Movie存储在Rental类的私有变量_movie中。 作者似乎忽略了这一点。


这些事情的真正含义是“类型信息通常是易变的”和“最小的涟漪效应(已实现)”? 这些模糊的词(volatile 和ripple)并没有清楚地解释这种重构的优势。

作者试图强调经常添加/删除/修改类型。 (不过,我的经验并非如此。类型在我处理过的那种代码中一直是稳定的。)并且在类型经常更新的情况下,您希望通过最小化这些更新的影响来将您的头痛降到最低。

如果对于每个“类型”更新,您必须在 2 万处修改代码,那么这不是“最小连锁效应”的好例子。

但是,我认为在这里遵循单一职责原则将比遵循作者建议的重构更有成效。 更改将是相同的,但是对于当前和未来的开发人员来说,找到必须进行这些更改的位置将更加直观。

在我看来,在 Java 中使用switch-case语句会导致代码的可读性下降。 由于 Java 是首选的面向对象语言,因此以explicit方式编写所有内容非常受欢迎。

在 Java 中使用switch语句和关键字break会导致编程为procedure方式。 一般来说,我们可以说很难理解break意味着什么。

因此,通过替换为If-else语句来重构switch语句将改善clean code

我只在 Java 中使用switch-case ,即使是在enum case 中,正如Effective Java一书(第 3 版,第 167 页)中推荐的那样。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM