简介
“提取类”(Extract Class)是一种重要的代码重构手法。当一个类承担了过多的职责,包含了大量的属性和方法时,这个类会变得复杂且难以维护。通过提取类,可以将原本一个类中不同职责的部分分离出来,创建新的类,使得每个类只负责单一的功能,从而提高代码的可读性、可维护性和可扩展性。
针对的症状(代码坏味道)
- 职责过重:一个类承担了多个不相关或关联度较低的职责,导致类的代码量过大,逻辑复杂。
- 代码重复:由于类的职责过多,可能会出现一些重复的代码逻辑,违反了代码复用原则。
- 难以理解和修改:类中的代码逻辑复杂,难以清晰地理解其功能,并且在修改时容易影响到其他不相关的功能。
提取类(Extract Class)的详细步骤
- 识别职责
- 分析原类的属性和方法,找出其中具有相对独立功能的部分,确定可以分离出来的职责。
- 创建新类
- 根据识别出的职责,创建一个新的类。新类的名称应能够清晰地描述其承担的职责。
- 移动属性和方法
- 将与新职责相关的属性和方法从原类中移动到新类中。
- 确保在移动过程中,处理好属性和方法之间的依赖关系,可能需要调整参数传递和返回值。
- 建立关联
- 在原类中保留对新类的引用,以便原类可以调用新类的功能。
- 可能需要在原类中添加一些方法来封装对新类的调用,使原类的接口保持稳定。
- 测试
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
- 手动测试:如果有必要,进行手动测试以验证功能的正确性。
- 代码审查
- 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
- 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明提取类的影响。
示例
假设有一个 Person 类,它不仅包含了个人的基本信息,还包含了处理地址信息的方法,职责过重。
原始代码
public class Person {
private String name;
private int age;
private String street;
private String city;
private String state;
private String zipCode;
public Person(String name, int age, String street, String city, String state, String zipCode) {
this.name = name;
this.age = age;
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getFullAddress() {
return street + ", " + city + ", " + state + " " + zipCode;
}
}
重构步骤
- 识别职责:地址信息的管理是一个相对独立的职责,可以从
Person类中分离出来。 - 创建新类:创建
Address类来管理地址信息。 - 移动属性和方法:将与地址相关的属性(
street、city、state、zipCode)和方法(getFullAddress)移动到Address类中。 - 建立关联:在
Person类中保留对Address类的引用。
重构后代码
// 新的 Address 类
class Address {
private String street;
private String city;
private String state;
private String zipCode;
public Address(String street, String city, String state, String zipCode) {
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getFullAddress() {
return street + ", " + city + ", " + state + " " + zipCode;
}
}
// 重构后的 Person 类
public class Person {
private String name;
private int age;
private Address address;
public Person(String name, int age, String street, String city, String state, String zipCode) {
this.name = name;
this.age = age;
this.address = new Address(street, city, state, zipCode);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getFullAddress() {
return address.getFullAddress();
}
}
练习
基础练习题
-
简单类提取
-
给定以下 Java 代码,
Student类包含了学生的基本信息和课程信息的管理,职责过重。请将课程信息的管理提取到一个新的类中。import java.util.ArrayList; import java.util.List; public class Student { private String name; private int age; private List<String> courses; public Student(String name, int age) { this.name = name; this.age = age; this.courses = new ArrayList<>(); } public String getName() { return name; } public int getAge() { return age; } public void addCourse(String course) { courses.add(course); } public List<String> getCourses() { return courses; } }
-
-
多职责类提取
- 下面的 Java 代码中,
Employee类包含了员工的个人信息、薪资计算和考勤管理,职责过多。请将薪资计算和考勤管理分别提取到新的类中。
public class Employee { private String name; private int age; private double hourlyRate; private int workingHours; private int absentDays; public Employee(String name, int age, double hourlyRate, int workingHours, int absentDays) { this.name = name; this.age = age; this.hourlyRate = hourlyRate; this.workingHours = workingHours; this.absentDays = absentDays; } public String getName() { return name; } public int getAge() { return age; } public double calculateSalary() { return hourlyRate * workingHours; } public int getAbsentDays() { return absentDays; } } - 下面的 Java 代码中,
进阶练习题
-
复杂类提取与关联处理
- 在这段 Java 代码中,
Library类包含了图书管理和借阅管理的职责。请将借阅管理提取到一个新的类中,并处理好两个类之间的关联。
import java.util.ArrayList; import java.util.List; class Book { private String title; private boolean isBorrowed; public Book(String title) { this.title = title; this.isBorrowed = false; } public String getTitle() { return title; } public boolean isBorrowed() { return isBorrowed; } public void borrow() { isBorrowed = true; } public void returnBook() { isBorrowed = false; } } public class Library { private List<Book> books; public Library() { this.books = new ArrayList<>(); } public void addBook(Book book) { books.add(book); } public void borrowBook(String title) { for (Book book : books) { if (book.getTitle().equals(title) && !book.isBorrowed()) { book.borrow(); System.out.println("Book borrowed: " + title); return; } } System.out.println("Book not available: " + title); } public void returnBook(String title) { for (Book book : books) { if (book.getTitle().equals(title) && book.isBorrowed()) { book.returnBook(); System.out.println("Book returned: " + title); return; } } System.out.println("Invalid return: " + title); } } - 在这段 Java 代码中,
-
类提取与接口设计
-
给定以下 Java 代码,
MediaPlayer类包含了音频和视频播放的功能。请将音频和视频播放功能分别提取到新的类中,并设计合适的接口来实现多态调用。public class MediaPlayer { public void playAudio(String audioFile) { System.out.println("Playing audio: " + audioFile); } public void playVideo(String videoFile) { System.out.println("Playing video: " + videoFile); } }
-
综合拓展练习题
- 多模块类提取与代码审查模拟
- 考虑一个简单的 Java 电商系统,有
Order类,它包含了订单信息管理、商品管理和支付管理的职责。请将商品管理和支付管理分别提取到新的类中。 - 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。
import java.util.ArrayList; import java.util.List; class Product { private String name; private double price; public Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } } public class Order { private String orderId; private List<Product> products; private double totalAmount; public Order(String orderId) { this.orderId = orderId; this.products = new ArrayList<>(); this.totalAmount = 0; } public String getOrderId() { return orderId; } public void addProduct(Product product) { products.add(product); totalAmount += product.getPrice(); } public void removeProduct(Product product) { if (products.remove(product)) { totalAmount -= product.getPrice(); } } public void processPayment() { System.out.println("Processing payment of $" + totalAmount + " for order " + orderId); } }
- 考虑一个简单的 Java 电商系统,有