简介
“引入空对象”(Introduce Null Object)是一种重构手法,通过创建一个表示“空”或“默认”行为的对象来替代 null 的使用,从而减少代码中的 null 检查并提高代码的可读性和可维护性。这种方法特别适用于那些需要频繁检查某个对象是否为 null 的情况。以下是进行“引入空对象”重构的详细步骤:
针对的症状(代码坏味道)
- 过多的
null检查(Excessive Null Checks) - 重复的条件逻辑(Repeated Conditional Logic)
引入空对象(Introduce Null Object)的详细步骤
- 识别需要处理的
null情况- 寻找代码中频繁出现的
null检查。 - 确定这些
null检查是否可以被封装到一个空对象中。
- 寻找代码中频繁出现的
- 创建空对象类
- 创建一个新的类来表示空对象。
- 为这个类实现与正常情况相同的接口或方法,但返回默认值或执行默认行为。
- 替换
null检查- 将代码中的
null检查替换为对空对象的使用。 - 确保所有相关的地方都使用新的空对象。
- 将代码中的
- 测试
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
- 手动测试:如果有必要,进行手动测试以验证功能的正确性。
- 代码审查
- 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
- 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明引入空对象的影响。
示例
假设有一个方法 getCustomerName,其中包含对 null 的频繁检查,我们希望对其进行“引入空对象”的重构:
public class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Order {
private Customer customer;
public Order(Customer customer) {
this.customer = customer;
}
public String getCustomerName() {
return (customer == null) ? "Unknown Customer" : customer.getName();
}
}
步骤如下:
- 识别需要处理的
null情况:- 频繁的
null检查:customer == null。
- 频繁的
- 创建空对象类:
-
创建一个新的类
NullCustomer来表示空对象:public class NullCustomer extends Customer { public NullCustomer() { super("Unknown Customer"); } }
-
- 替换
null检查:-
将
null检查替换为对NullCustomer的使用:public class Order { private Customer customer; public Order(Customer customer) { this.customer = (customer == null) ? new NullCustomer() : customer; } public String getCustomerName() { return customer.getName(); } }
-
- 测试:
- 编译代码:确保代码编译通过,没有任何语法错误。
- 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
- 代码审查:
- 让同事审查代码,确保没有引入新的问题。
练习
基础练习题
- 引入简单空对象
-
给定以下 Java 代码,getProductPrice方法中有频繁的
null检查。请引入空对象来封装这些检查。public class Product { private double price; public Product(double price) { this.price = price; } public double getPrice() { return price; } } public class Order { private Product product; public Order(Product product) { this.product = product; } public double getProductPrice() { return (product == null) ? 0.0 : product.getPrice(); } }
-
- 引入复杂空对象
-
下面的 Java 代码中有两个方法getCustomerName和getCustomerAddress,都包含频繁的
null检查。请引入空对象来封装这些检查。public class Customer { private String name; private String address; public Customer(String name, String address) { this.name = name; this.address = address; } public String getName() { return name; } public String getAddress() { return address; } } public class Order { private Customer customer; public Order(Customer customer) { this.customer = customer; } public String getCustomerName() { return (customer == null) ? "Unknown Customer" : customer.getName(); } public String getCustomerAddress() { return (customer == null) ? "Unknown Address" : customer.getAddress(); } }
-
进阶练习题
- 引入复杂逻辑空对象
-
在这段 Java 代码中,calculateShippingCost方法内有频繁的
null检查。请引入空对象来封装这些检查。public class ShippingInfo { private double baseShippingCost; private double discountRate; public ShippingInfo(double baseShippingCost, double discountRate) { this.baseShippingCost = baseShippingCost; this.discountRate = discountRate; } public double getBaseShippingCost() { return baseShippingCost; } public double getDiscountRate() { return discountRate; } } public class Order { private ShippingInfo shippingInfo; public Order(ShippingInfo shippingInfo) { this.shippingInfo = shippingInfo; } public double calculateShippingCost() { return (shippingInfo == null) ? 0.0 : shippingInfo.getBaseShippingCost() - (shippingInfo.getBaseShippingCost() * shippingInfo.getDiscountRate()); } }
-
- 引入方法与返回值空对象
-
给定以下 Java 代码,processData方法包含频繁的
null检查。请引入空对象来封装这些检查。import java.util.ArrayList; import java.util.List; public class DataProcessor { public int processData(int[] dataArray) { if (dataArray == null) { return 0; } List<Integer> processedData = new ArrayList<>(); for (int num : dataArray) { num = num * 2; if (num > 10) { num = num - 5; } processedData.add(num); } int sum = 0; for (int num : processedData) { sum += num; } return sum; } }
-
综合拓展练习题
- 多模块引入空对象与代码审查模拟
-
考虑一个简单的 Java 电商系统,有Product类、Cart类和Order类。Cart类中的calculateCartTotal方法和Order类中的calculateOrderTotal方法都有频繁的
null检查,同时Cart类中的applyCartDiscount方法也有频繁的null检查。 -
请对这些方法进行 “引入空对象” 重构,将频繁的
null检查封装到空对象中。 -
假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。
class Product { private double price; public Product(double price) { this.price = price; } public double getPrice() { return price; } } class Cart { private Product[] products; private double discountRate; public Cart(Product[] products, double discountRate) { this.products = products; this.discountRate = discountRate; } public double calculateCartTotal() { if (products == null) { return 0.0; } double total = 0; for (Product product : products) { if (product == null) { continue; } total += product.getPrice(); } return total - (total * discountRate); } public void applyCartDiscount() { if (products == null) { return; } if (products.length > 3) { discountRate += 0.05; } if (calculateCartTotal() > 100) { discountRate += 0.1; } } } class Order { private Product[] products; private double shippingCost; public Order(Product[] products, double shippingCost) { this.products = products; this.shippingCost = shippingCost; } public double calculateOrderTotal() { if (products == null) { return 0.0; } double total = 0; for (Product product : products) { if (product == null) { continue; } total += product.getPrice(); } return total + shippingCost; } }
-