重构手法——对象结构优化类 | 类关系解耦 | 提取类

87 阅读5分钟

简介

“提取类”(Extract Class)是一种重要的代码重构手法。当一个类承担了过多的职责,包含了大量的属性和方法时,这个类会变得复杂且难以维护。通过提取类,可以将原本一个类中不同职责的部分分离出来,创建新的类,使得每个类只负责单一的功能,从而提高代码的可读性、可维护性和可扩展性。

针对的症状(代码坏味道)

  • 职责过重:一个类承担了多个不相关或关联度较低的职责,导致类的代码量过大,逻辑复杂。
  • 代码重复:由于类的职责过多,可能会出现一些重复的代码逻辑,违反了代码复用原则。
  • 难以理解和修改:类中的代码逻辑复杂,难以清晰地理解其功能,并且在修改时容易影响到其他不相关的功能。

提取类(Extract Class)的详细步骤

  1. 识别职责
    • 分析原类的属性和方法,找出其中具有相对独立功能的部分,确定可以分离出来的职责。
  2. 创建新类
    • 根据识别出的职责,创建一个新的类。新类的名称应能够清晰地描述其承担的职责。
  3. 移动属性和方法
    • 将与新职责相关的属性和方法从原类中移动到新类中。
    • 确保在移动过程中,处理好属性和方法之间的依赖关系,可能需要调整参数传递和返回值。
  4. 建立关联
    • 在原类中保留对新类的引用,以便原类可以调用新类的功能。
    • 可能需要在原类中添加一些方法来封装对新类的调用,使原类的接口保持稳定。
  5. 测试
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
    • 手动测试:如果有必要,进行手动测试以验证功能的正确性。
  6. 代码审查
    • 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
    • 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明提取类的影响。

示例

假设有一个 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;
    }
}

重构步骤

  1. 识别职责:地址信息的管理是一个相对独立的职责,可以从 Person 类中分离出来。
  2. 创建新类:创建 Address 类来管理地址信息。
  3. 移动属性和方法:将与地址相关的属性(streetcitystatezipCode)和方法(getFullAddress)移动到 Address 类中。
  4. 建立关联:在 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();
    }
}

练习

基础练习题

  1. 简单类提取

    • 给定以下 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;
          }
      }
      
  2. 多职责类提取

    • 下面的 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;
        }
    }
    

进阶练习题

  1. 复杂类提取与关联处理

    • 在这段 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);
        }
    }
    
  2. 类提取与接口设计

    • 给定以下 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);
          }
      }
      

综合拓展练习题

  1. 多模块类提取与代码审查模拟
    • 考虑一个简单的 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);
          }
      }