简介
“Change Value to Reference”(将值更改为引用)是一种重构手法,当多个值对象具有相同的状态且你希望避免重复存储相同数据时,此重构方法较为适用。通过将值对象转换为引用对象,可以减少内存消耗,并便于对共享状态进行集中管理。以下是进行“Change Value to Reference”重构的详细步骤。
针对的症状(代码坏味道)
- 值对象重复:存在多个值对象,它们具有相同的状态,导致数据冗余。
- 需要共享状态:希望对这些具有相同状态的值对象进行集中管理,共享它们的状态。
“Change Value to Reference”的详细步骤
- 识别值对象
- 查找重复值对象:在代码中寻找具有相同状态的多个值对象。
- 评估共享可能性:判断这些值对象是否可以通过共享状态来优化,确保共享状态不会对原有功能产生负面影响。
- 创建引用对象
- 设计引用类:创建一个新的类作为引用对象,该类应包含值对象中的所有状态属性。
- 提供必要方法:为引用类提供访问和修改状态的方法,例如getter和setter方法。
- 替换值对象为引用对象
- 更新数据结构:在使用值对象的地方,将其替换为引用对象。这可能涉及到修改类的成员变量类型、方法参数和返回值类型等。
- 调整相关逻辑:由于引用对象的特性,可能需要修改与值对象交互的逻辑,例如比较对象时从比较值改为比较引用。
- 管理引用对象的生命周期
- 对象创建与缓存:决定如何创建引用对象,并考虑是否需要使用缓存机制来管理引用对象的实例,以确保相同状态的对象只有一个实例。
- 内存管理:确保在不再需要引用对象时,正确处理其内存释放,避免内存泄漏。
- 测试
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
- 手动测试:如果有必要,进行手动测试以验证功能的正确性,特别是涉及到对象引用和共享状态的部分。
- 代码审查
- 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降,特别是在处理对象引用和共享状态方面。
- 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明将值更改为引用的影响。
示例
假设我们有一个AddressValue类,当前以值对象的形式被多个Customer对象使用,并且存在许多重复的地址信息,我们希望将其更改为引用对象。
原始代码(值对象形式)
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;
}
}
class Customer {
private AddressValue address;
public Customer(AddressValue address) {
this.address = address;
}
public AddressValue getAddress() {
return address;
}
}
重构步骤
-
识别值对象:
AddressValue类是值对象,并且多个Customer对象可能拥有相同的地址,存在数据冗余。 -
创建引用对象:
- 创建
AddressReference类:class AddressReference { private String street; private String city; public AddressReference(String street, String city) { this.street = street; this.city = city; } public String getStreet() { return street; } public String getCity() { return city; } }
- 创建
-
替换值对象为引用对象:修改
Customer类,使用AddressReference代替AddressValue:class Customer { private AddressReference address; public Customer(AddressReference address) { this.address = address; } public AddressReference getAddress() { return address; } } -
管理引用对象的生命周期:可以考虑使用一个
AddressRegistry类来管理AddressReference对象的创建和缓存,例如:class AddressRegistry { private static Map<String, AddressReference> addressCache = new HashMap<>(); public static AddressReference getAddress(String street, String city) { String key = street + "_" + city; if (!addressCache.containsKey(key)) { AddressReference address = new AddressReference(street, city); addressCache.put(key, address); } return addressCache.get(key); } }在创建
Customer对象时,通过AddressRegistry获取AddressReference对象:public class Main { public static void main(String[] args) { AddressReference address1 = AddressRegistry.getAddress("123 Main St", "Anytown"); AddressReference address2 = AddressRegistry.getAddress("123 Main St", "Anytown"); Customer customer1 = new Customer(address1); Customer customer2 = new Customer(address2); // address1 和 address2 是同一个对象 } } -
测试:
- 编译代码,确保没有语法错误。
- 运行相关单元测试,验证功能是否正常,特别是
AddressRegistry的缓存功能以及Customer类与AddressReference类的交互。
-
代码审查:
- 邀请同事审查代码,确保在处理对象引用和共享状态方面没有引入新问题。
- 更新文档,说明
Address已从值对象改为引用对象,并介绍AddressRegistry的使用。
练习
基础练习题
-
简单值对象转换
-
给定以下 Java 代码,
ColorValue类作为值对象被Shape类使用。请将ColorValue类转换为引用对象ColorReference。class ColorValue { private String name; private int red; private int green; private int blue; public ColorValue(String name, int red, int green, int blue) { this.name = name; this.red = red; this.green = green; this.blue = blue; } public String getName() { return name; } public int getRed() { return red; } public int getGreen() { return green; } public int getBlue() { return blue; } } class Shape { private ColorValue color; public Shape(ColorValue color) { this.color = color; } public ColorValue getColor() { return color; } }
-
-
多层值对象转换
-
下面的 Java 代码中有
Product类使用CategoryValue类,CategoryValue类又使用SubCategoryValue类。请将SubCategoryValue类和CategoryValue类转换为引用对象SubCategoryReference和CategoryReference,并相应调整Product类。class SubCategoryValue { private String name; public SubCategoryValue(String name) { this.name = name; } public String getName() { return name; } } class CategoryValue { private String name; private SubCategoryValue subCategory; public CategoryValue(String name, SubCategoryValue subCategory) { this.name = name; this.subCategory = subCategory; } public String getName() { return name; } public SubCategoryValue getSubCategory() { return subCategory; } } class Product { private String name; private CategoryValue category; public Product(String name, CategoryValue category) { this.name = name; this.category = category; } public String getName() { return name; } public CategoryValue getCategory() { return category; } }
-
进阶练习题
-
值对象转换与逻辑调整
-
在这段 Java 代码中,
OrderItem类使用ProductValue类,并且在Order类的calculateTotal方法中有基于ProductValue的逻辑。请将ProductValue类转换为引用对象ProductReference,并调整calculateTotal方法的逻辑。class ProductValue { private String name; private double price; public ProductValue(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } } class OrderItem { private ProductValue product; private int quantity; public OrderItem(ProductValue product, int quantity) { this.product = product; this.quantity = quantity; } public ProductValue getProduct() { return product; } public int getQuantity() { return quantity; } } class Order { private List<OrderItem> items; public Order(List<OrderItem> items) { this.items = items; } public double calculateTotal() { double total = 0; for (OrderItem item : items) { total += item.getProduct().getPrice() * item.getQuantity(); } return total; } }
-
-
引用对象的缓存与管理
-
给定以下 Java 代码,
EmployeeValue类作为值对象被Department类使用。请将EmployeeValue类转换为引用对象EmployeeReference,并实现一个缓存机制来管理EmployeeReference对象的实例,同时确保在Department类中正确使用缓存的对象。class EmployeeValue { private String name; private int age; public EmployeeValue(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } class Department { private List<EmployeeValue> employees; public Department(List<EmployeeValue> employees) { this.employees = employees; } public List<EmployeeValue> getEmployees() { return employees; } }
-
综合拓展练习题
- 多类值对象重构与代码审查模拟
- 考虑一个简单的 Java 在线商城系统,有
Product类、Order类、Cart类。Product类使用BrandValue类和CategoryValue类作为值对象,Order类和Cart类都包含处理Product对象的逻辑。 - 请对这些类进行“Change Value to Reference”重构,将
BrandValue类和CategoryValue类转换为引用对象,并调整相关类的逻辑。 - 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。
class BrandValue { private String name; private String countryOfOrigin; public BrandValue(String name, String countryOfOrigin) { this.name = name; this.countryOfOrigin = countryOfOrigin; } public String getName() { return name; } public String getCountryOfOrigin() { return countryOfOrigin; } } class CategoryValue { private String name; public CategoryValue(String name) { this.name = name; } public String getName() { return name; } } class Product { private String name; private BrandValue brand; private CategoryValue category; private double price; public Product(String name, BrandValue brand, CategoryValue category, double price) { this.name = name; this.brand = brand; this.category = category; this.price = price; } public String getName() { return name; } public BrandValue getBrand() { return brand; } public CategoryValue getCategory() { return category; } public double getPrice() { return price; } } class Cart { private List<Product> products; public Cart(List<Product> products) { this.products = products; } public double calculateTotal() { double total = 0; for (Product product : products) { total += product.getPrice(); } return total; } } class Order { private List<Product> products; public Order(List<Product> products) { this.products = products; } public double calculateTotal() { double total = 0; for (Product product : products) { total += product.getPrice(); } return total; } }
- 考虑一个简单的 Java 在线商城系统,有