Contents

在 Eclipse 中进行重构

1. 概述

refactoring.com 上,我们读到“重构是一种规范的技术,用于重构现有代码体,在不改变其外部行为的情况下改变其内部结构。”

通常,我们可能想要重命名变量或方法,或者我们可能想要通过引入设计模式使我们的代码更加面向对象。现代 IDE 具有许多内置功能,可帮助我们实现这些重构目标和许多其他目标。

在本教程中,我们将专注于在 Eclipse 中进行重构,这是一个免费的流行 Java IDE。

在我们开始任何重构之前,建议有一套可靠的测试,以检查我们在重构时没有破坏任何东西。

2. 重命名

2.1. 重命名变量和方法

我们可以按照以下简单步骤重命名变量和方法

    • 选择元素
      • 右键单击元素
      • 单击重构 > 重命名选项
  • 输入新名称
  • 回车

/uploads/eclipse_refactoring/1.png

我们还可以使用快捷键Alt + Shift + R执行第二步和第三步。

执行上述操作时,Eclipse 将在该文件中查找该元素的所有用法并将它们全部替换。

我们还可以使用高级功能来**更新其他类中的引用,**方法是在重构打开时将鼠标悬停在项目上并单击Options

/uploads/eclipse_refactoring/3.png

这将打开一个弹出窗口,我们既可以重命名变量或方法,也可以选择更新其他类中的引用:

/uploads/eclipse_refactoring/5.png

2.2. 重命名包

我们可以通过选择包名称并执行与前面示例中相同的操作来重命名包。将立即出现一个弹出窗口,我们可以在其中重命名包,其中包含更新引用和重命名子包等选项。

/uploads/eclipse_refactoring/7.png

我们还可以通过按 F2 从 Project Explorer 视图重命名包:

/uploads/eclipse_refactoring/9.png

2.3. 重命名类和接口

我们可以通过使用相同的操作或仅通过在 Project Explorer 中按F2来重命名类或接口。这将打开一个弹出窗口,其中包含更新参考的选项以及一些高级选项:

/uploads/eclipse_refactoring/11.png

3. 提取

现在,让我们谈谈提取。提取代码意味着获取一段代码并移动它。

例如,我们可以将代码提取到不同的类、超类或接口中。我们甚至可以将代码提取到同一类中的变量或方法中。

Eclipse 提供了多种实现提取的方法,我们将在以下部分中进行演示。

3.1. 提取类

假设我们的代码库中有以下Car类:

public class Car {
    private String licensePlate;
    private String driverName;
    private String driverLicense;
    public String getDetails() {
        return "Car [licensePlate=" + licensePlate + ", driverName=" + driverName
          + ", driverLicense=" + driverLicense + "]";
    }
    // getters and setters
}

现在,假设我们要将驱动程序详细信息提取到不同的类。我们可以通过右键单击类中的任意位置并选择Refactor > Extract Class选项来做到这一点:

/uploads/eclipse_refactoring/13.png

这将打开一个弹出窗口,我们可以在其中命名类并选择要移动的字段,以及其他一些选项:

/uploads/eclipse_refactoring/15.png

**我们还可以在继续之前预览代码。**当我们点击OK时,Eclipse 将创建一个名为Driver的新类,之前的代码将被重构为:

public class Car {
    private String licensePlate;
    private Driver driver = new Driver();
    public String getDetails() {
        return "Car [licensePlate=" + licensePlate + ", driverName=" + driver.getDriverName()
          + ", driverLicense=" + driver.getDriverLicense() + "]";
    }
    //getters and setters
}

3.2. 提取接口

我们也可以以类似的方式提取接口。假设我们有以下EmployeeService类:

public class EmployeeService {
    public void save(Employee emp) {
    }
    public void delete(Employee emp) {
    }
    public void sendEmail(List<Integer> ids, String message) {
    }
}

我们可以通过**右键单击类中的任意位置并选择Refactor > Extract Interface选项来提取接口,**也可以使用Alt + Shift + T快捷键命令直接调出菜单:

/uploads/eclipse_refactoring/17.png

这将打开一个弹出窗口,我们可以在其中输入接口名称并决定在接口中声明哪些成员:

/uploads/eclipse_refactoring/19.png

作为重构的结果,我们将拥有一个接口IEmpService,并且我们的EmployeeService类也将被更改:

public class EmployeeService implements IEmpService {
    @Override
    public void save(Employee emp) {
    }
    @Override
    public void delete(Employee emp) {
    }
    public void sendEmail(List<Integer> ids, String message) {
    }
}

3.3. 提取超类

假设我们有一个Employee类,其中包含几个不一定与该人的就业有关的属性:

public class Employee {
    private String name;
    private int age;
    private int experienceInMonths;
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public int getExperienceInMonths() {
        return experienceInMonths;
    }
}

我们可能希望将与就业无关的属性提取到Person超类。要将项目提取到超类,我们可以  右键单击类中的任意位置并选择“重构”>“提取超类”选项,或者使用 Alt + Shift + T直接调出菜单:

/uploads/eclipse_refactoring/21.png

这将使用我们选择的变量和方法创建一个新的Person类,并且Employee类将被重构为:

public class Employee extends Person {
    private int experienceInMonths;
    public int getExperienceInMonths() {
        return experienceInMonths;
    }
}

3.4. 提取方法

有时,我们可能希望将方法中的某段代码提取到不同的方法中,以保持代码干净且易于维护。

例如,假设我们的方法中嵌入了一个 for 循环:

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}

要调用提取方法向导,我们需要执行以下步骤:

  • 选择我们要提取的代码行
  • 右键单击所选区域
  • 单击重构 > 提取方法选项

/uploads/eclipse_refactoring/23.png

**最后两个步骤也可以通过键盘快捷键Alt + Shift + M来实现。**让我们看看提取方法对话框:

/uploads/eclipse_refactoring/25.png

这会将我们的代码重构为:

public class Test {
    public static void main(String[] args) {
        printArgs(args);
    }
    private static void printArgs(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}

3.5. 提取局部变量

我们可以将某些项目提取为局部变量,以使我们的代码更具可读性。

当我们有一个string文字时,这很方便:

public class Test {
    public static void main(String[] args) {
        System.out.println("Number of Arguments passed =" + args.length);
    }
}

我们想把它提取到一个局部变量中。

为此,我们需要:

  • 选择项目
  • 右键单击并选择重构 > 提取局部变量

/uploads/eclipse_refactoring/27.png

**最后一步也可以通过键盘快捷键Alt + Shift + L来实现。**现在,我们可以提取我们的局部变量:

/uploads/eclipse_refactoring/29.png

这是重构的结果:

public class Test {
    public static void main(String[] args) {
        final String prefix = "Number of Arguments passed =";
        System.out.println(prefix + args.length);
    }
}

3.6. 提取常数

或者,我们可以将表达式和文字值提取到static final类属性中。

我们可以将3.14的值提取到一个局部变量中,正如我们刚刚看到的:

public class MathUtil {
    public double circumference(double radius) {
        return 2 * 3.14 * radius;
    }
}

但是,最好将其提取为常量,为此我们需要:

  • 选择项目
  • 右键单击并选择**“重构”>“提取常量”**

/uploads/eclipse_refactoring/31.png

这将打开一个对话框,我们可以在其中为常量指定名称并设置其可见性,以及其他几个选项:

/uploads/eclipse_refactoring/33.png

现在,我们的代码看起来更具可读性:

public class MathUtil {
    private static final double PI = 3.14;
    public double circumference(double radius) {
        return 2 * PI * radius;
    }
}

4. 内联

我们也可以采用另一种方式并内联代码。

考虑一个 Util 类,它有一个仅使用一次的局部变量:

public class Util {
    public void isNumberPrime(int num) {
        boolean result = isPrime(num);
        if (result) {
            System.out.println("Number is Prime");
        } else {
            System.out.println("Number is Not Prime");
        }
    }
    // isPrime method
}

我们想要删除result局部变量并内联isPrime方法调用。为此,我们按照以下步骤操作:

  • 选择我们要内联的项目
  • 右键单击并选择“Refactor > Inline”选项

/uploads/eclipse_refactoring/35.png

最后一步也可以通过键盘快捷键Alt + Shift + I来实现:

/uploads/eclipse_refactoring/37.png

之后,我们就少了一个需要跟踪的变量:

public class Util {
    public void isNumberPrime(int num) {
        if (isPrime(num)) {
            System.out.println("Number is Prime");
        } else {
            System.out.println("Number is Not Prime");
        }
    }
    // isPrime method
}

5. Push Down and Pull Up

如果我们的类之间有父子关系(就像我们之前的EmployeePerson示例),并且我们想要在它们之间移动某些方法或变量,我们可以使用 Eclipse 提供的推/拉选项。

顾名思义,Push Down选项将方法和字段从父类移动到所有子类,而Pull Up选项将方法和字段从特定子类移动到父类,从而使该方法可用于所有子类。

要将方法向下移动到子类,我们需要右键单击类中的任意位置,然后选择“Refactor > Push Down”选项:

/uploads/eclipse_refactoring/39.png

这将打开一个向导,我们可以在其中选择要Push Down的项目:

/uploads/eclipse_refactoring/41.png

同样,要将方法从子类移动到父类,我们需要右键单击类中的任意位置并选择Refactor > Pull Up

/uploads/eclipse_refactoring/43.png

这将打开一个类似的向导,我们可以在其中选择要提取的项目:

/uploads/eclipse_refactoring/45.png

6. 更改方法签名

要更改现有方法的方法签名,我们可以遵循几个简单的步骤:

  • 选择方法或将光标放在内部某处
  • 右键单击并选择“Refactor > Change Method Signature

最后一步也可以通过键盘快捷键Alt + Shift + C来实现。

这将打开一个弹出窗口,您可以在其中相应地更改方法签名:

/uploads/eclipse_refactoring/47.png

7. 移动

有时,我们只是想将方法移动到另一个现有的类,以使我们的代码更加面向对象。

考虑我们有一个Movie类的场景:

public class Movie {
    private String title;
    private double price;
    private MovieType type;
    // other methods
}

MovieType是一个简单的枚举:

public enum MovieType {
    NEW, REGULAR
}

还假设我们有一个要求,如果Customer租了一部NEW的电影,则会多收费两美元,并且我们的Customer类具有以下逻辑来计算totalCost()

public class Customer {
    private String name;
    private String address;
    private List<Movie> movies;
    public double totalCost() {
        double result = 0;
        for (Movie movie : movies) {
            result += movieCost(movie);
        }
        return result;
    }
    private double movieCost(Movie movie) {
        if (movie.getType()
            .equals(MovieType.NEW)) {
            return 2 + movie.getPrice();
        }
        return movie.getPrice();
    }
    // other methods
}

显然,基于MovieType的电影成本计算放在Movie类而不是Customer类中更为合适。我们可以轻松地在 Eclipse 中移动这个计算逻辑:

  • 选择要移动的行
  • 右键单击并选择“Refactor > Move”选项

最后一步也可以通过键盘快捷键Alt + Shift + V来实现:

/uploads/eclipse_refactoring/49.png

Eclipse 足够聪明,能够意识到这个逻辑应该在我们的Movie类中。如果需要,我们可以更改方法名称以及其他高级选项。

最终的Customer类代码将重构为:

public class Customer {
    private String name;
    private String address;
    private List<Movie> movies;
    public double totalCost() {
        double result = 0;
        for (Movie movie : movies) {
            result += movie.movieCost();
        }
        return result;
    }
    // other methods
}

正如我们所看到的,movieCost方法已移至我们的Movie类中,并在重构的Customer类中使用。