重构手法——数据重组类 | 数据关系重构 | 将引用更改为值

83 阅读5分钟

简介

“Change Reference to Value”(将引用更改为值)是一种重构手法,当对象的标识(通过引用)比其实际状态更重要时,这种重构就很有用。通过将对象的引用替换为对象的值,可以简化对象之间的关系,提高代码的可维护性和可读性。以下是进行“Change Reference to Value”重构的详细步骤。

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

  • 对象引用复杂:对象之间的引用关系复杂,导致难以理解和维护代码。
  • 标识比状态更重要:对象的标识(通过引用)在代码逻辑中起关键作用,而不是对象的实际状态。

“Change Reference to Value”的详细步骤

  1. 确定要更改的对象
    • 分析引用关系:在代码中找到引用关系复杂的对象,并且确定其标识是否比状态更重要。
    • 评估对象状态:确保该对象的状态相对稳定,不会频繁变化导致值的不一致。
  2. 创建值对象
    • 复制状态:创建一个新的类,该类将作为值对象,复制原对象中需要的值属性到新类。
    • 提供必要方法:为新的值对象提供必要的访问方法(getter)和修改方法(setter),如果需要的话。
  3. 替换引用
    • 更新引用处:在原代码中,找到所有引用原对象的地方,将其替换为使用新创建的值对象。
    • 修改逻辑:由于值对象是不可变的(通常情况下),可能需要修改依赖原对象可变状态的逻辑。
  4. 测试
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
    • 手动测试:如果有必要,进行手动测试以验证功能的正确性。
  5. 代码审查
    • 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
    • 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明将引用更改为值的影响。

示例

假设我们有一个Customer类,当前使用引用方式处理客户信息,我们希望将其更改为值对象方式。

原始代码

class Address {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public String getCity() {
        return city;
    }
}

class Customer {
    private Address address;

    public Customer(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }
}

重构步骤

  1. 确定要更改的对象Customer类中的Address对象引用关系较为简单,且Address的状态相对稳定,适合进行“Change Reference to Value”重构。

  2. 创建值对象

    • 创建新的AddressValue类:
      class AddressValue {
        private String street;
        private String city;
      
        public AddressValue(String street, String city) {
           this.street = street;
           this.city = city;
        }
      
        public String getStreet() {
           return street;
        }
      
        public String getCity() {
           return city;
        }
      }
      
  3. 替换引用:修改Customer类,使用AddressValue代替Address

       class Customer {
          private AddressValue address;
    
       public Customer(AddressValue address) {
          this.address = address;
       }
    
       public AddressValue getAddress() {
          return address;
       }
       }
    
  4. 测试

    • 编译代码,确保没有语法错误。
    • 运行相关单元测试,验证功能是否正常。
  5. 代码审查

    • 邀请同事审查代码,确保重构没有引入新问题。
    • 更新文档,说明Address已从引用对象改为值对象。

练习

基础练习题

  1. 简单对象引用更改

    • 给定以下 Java 代码,Employee类中使用了Department类的引用。请将Department类的引用更改为值对象。

      class Department {
         private String name;
      
         public Department(String name) {
            this.name = name;
         }
      
         public String getName() {
            return name;
         }
      }
      
      class Employee {
         private Department department;
      
         public Employee(Department department) {
            this.department = department;
         }
      
         public Department getDepartment() {
            return department;
         }
      }
      
  2. 多层引用更改

    • 下面的 Java 代码中有Order类引用Product类,Product类又引用Category类。请将Category类的引用在Product 类中改为值对象,并相应调整Order类。

        class Category {
         private String name;
      
         public Category(String name) {
            this.name = name;
         }
      
         public String getName() {
            return name;
         }
      }
      
      class Product {
         private String name;
         private Category category;
      
         public Product(String name, Category category) {
            this.name = name;
            this.category = category;
         }
      
         public String getName() {
            return name;
         }
      
         public Category getCategory() {
            return category;
         }
      }
      
      class Order {
         private Product product;
      
         public Order(Product product) {
            this.product = product;
         }
      
         public Product getProduct() {
            return product;
         }
      }
      

进阶练习题

  1. 引用更改与逻辑调整

    • 在这段 Java 代码中,Project类引用Team类,并且在Project类的assignTeam方法中有基于Team引用的逻辑。请将Team 类的引用改为值对象,并调整assignTeam方法的逻辑。

      class Team {
         private String name;
         private int memberCount;
      
         public Team(String name, int memberCount) {
            this.name = name;
            this.memberCount = memberCount;
         }
      
         public String getName() {
            return name;
         }
      
         public int getMemberCount() {
            return memberCount;
         }
      }
      
      class Project {
         private Team team;
      
         public Project() {
            this.team = null;
         }
      
         public void assignTeam(Team team) {
            if (this.team == null) {
               this.team = team;
            } else {
               System.out.println("Team already assigned.");
            }
         }
      
         public Team getTeam() {
            return team;
         }
      }
      
  2. 值对象的不变性处理

    • 给定以下 Java 代码,User类引用Role类。请将Role类的引用改为值对象,并确保值对象的不变性,同时处理User类中与 Role相关的逻辑。

      class Role {
         private String name;
         private boolean isAdmin;
      
         public Role(String name, boolean isAdmin) {
            this.name = name;
            this.isAdmin = isAdmin;
         }
      
         public String getName() {
            return name;
         }
      
         public boolean isAdmin() {
            return isAdmin;
         }
      
         public void setAdmin(boolean isAdmin) {
            this.isAdmin = isAdmin;
         }
      }
      
      class User {
         private Role role;
      
         public User(Role role) {
            this.role = role;
         }
      
         public Role getRole() {
            return role;
         }
      
         public boolean hasAdminAccess() {
            return role.isAdmin();
         }
      }
      

综合拓展练习题

  1. 多类引用重构与代码审查模拟
    • 考虑一个简单的 Java 图书馆系统,有Book类、Author类、Publisher类。Book类引用Author类和Publisher类,Author 类又引用Publisher类。同时,Book类中的printBookInfo方法和Author类中的printAuthorInfo方法都有基于这些引用的复杂逻辑。

    • 请对这些类进行“Change Reference to Value”重构,将合适的引用改为值对象,并调整相关方法的逻辑。

    • 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。

      class Publisher {
         private String name;
         private String address;
      
         public Publisher(String name, String address) {
            this.name = name;
            this.address = address;
         }
      
         public String getName() {
            return name;
         }
      
         public String getAddress() {
            return address;
         }
      }
      
      class Author {
         private String name;
         private Publisher publisher;
      
         public Author(String name, Publisher publisher) {
            this.name = name;
            this.publisher = publisher;
         }
      
         public String getName() {
            return name;
         }
      
         public Publisher getPublisher() {
            return publisher;
         }
      
         public void printAuthorInfo() {
            System.out.println("Author: " + name + ", Publisher: " + publisher.getName());
         }
      }
      
      class Book {
         private String title;
         private Author author;
         private Publisher publisher;
      
         public Book(String title, Author author, Publisher publisher) {
            this.title = title;
            this.author = author;
            this.publisher = publisher;
         }
      
         public String getTitle() {
            return title;
         }
      
         public Author getAuthor() {
            return author;
         }
      
         public Publisher getPublisher() {
            return publisher;
         }
      
         public void printBookInfo() {
            System.out.println("Book: " + title + ", Author: " + author.getName() + ", Publisher: " + publisher.getName());
         }
      }