Java 重构开关语句-使用对象与基本数据类型

Java 重构开关语句-使用对象与基本数据类型,java,refactoring,Java,Refactoring,我正在通过这本书学习如何重构Java代码。作者进行了如下所示的重构,并解释了他为什么这么做,我对此并不清楚。有人能帮我理解这个解释吗?代码用于电影租赁软件 public class Movie { //Lets call these constants "priceCode". public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static f

我正在通过这本书学习如何重构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;
  }
 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;
   }
我们将上述方法代码移动到已经存在的Movie类中

重构后:

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;
  }
 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;
   }
作者解释:

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;
  }
 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语句,它应该在您自己的数据上,而不是在其他人的数据上

为此,为了工作,我必须输入租金的长度,这当然是来自租金的数据。该方法有效地使用了两个数据,即租赁的长度和电影的类型。 为什么我更喜欢将租赁时间长度传递给电影,而不是将电影类型传递给租赁?这是因为提议的更改都是关于添加新类型的。类型信息通常更不稳定。如果我改变电影类型,我希望涟漪效应最小,所以我更喜欢计算电影中的电荷

我的问题:

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;
  }
 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;
   }
为什么基于另一个对象的属性进行切换是个坏主意

这些东西到底意味着什么“类型信息通常是不稳定的”和“最少的涟漪效应(实现)”?这些模糊的词语(volatile和ripple)并不能清楚地解释这种重构的优势

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

为什么基于另一个对象的属性进行切换是个坏主意

事实并非如此

一般来说,作者所指的似乎是在较长时间内维护的代码库中可能发生的变化,以及对该代码库进行修改的困难。至少可以想象,它们可能是正确的,但这不是任何一种通用的或普遍接受的软件开发原则。

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


回答您的问题: 为什么基于另一个对象的属性进行切换是个坏主意

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

例如:
rent
getCharge()
方法将返回不同的值,如果
Movie
getPriceCode()
在调用之间更改了电影的价格代码,则该方法将返回不同的值

但是,它不适用于此场景,因为
Movie
存储在
rent
类的私有变量
\u Movie
中。作者似乎忽略了这一点


这些东西到底意味着什么“类型信息通常是不稳定的”和“最少的涟漪效应(实现)”?这些模糊的词语(volatile和ripple)并不能清楚地解释这种重构的优势

作者试图强调类型经常被添加/删除/修改。(但我的经验是相反的。类型在我工作过的代码中是稳定的。)在类型经常被更新的情况下,您希望通过最小化这些更新的影响来将您的头痛降到最低

如果每次“类型”更新都需要在20000处修改代码,那么这不是“最小涟漪效应”的好例子

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

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


处理“作者的解释”: 基于另一个对象的属性进行切换是个坏主意。如果必须使用switch语句,它应该在您自己的数据上,而不是在其他人的数据上

我对这种说法感到惊讶,因为
Movie
rent
class的一个私有变量。因此,切换实际上不会发生在另一个对象的属性上。它发生在私有对象的属性上

作者的重构代码也违反了单一责任原则<代码>电影应仅负责与电影相关的信息,不得加载租赁信息。因此,
getCharge()
方法似乎在
Rental
类中的正确位置


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

似乎又错了。根据我的经验,即使在非电影环境中,
Type
s也是相当稳定的常数。汽车的种类相当稳定——“汽车、轿车”、“汽车、SUV”等


类型信息通常更不稳定。如果我改变电影类型,我希望涟漪效应最小,所以我更喜欢计算电影中的电荷

要本地化类型的波动性,应将特定类型设置为枚举,在使用它的每个类之外。示例:在
Movie
rent之外创建
MovieType
枚举