重构手法——函数行为重塑类 | 语意 | 用查询替换派生变量

55 阅读2分钟

简介

"用查询替换派生变量"是一种消除冗余状态管理的重构手法。通过将可通过其他数据计算得出的变量替换为即时计算的方法,可以简化代码结构并减少状态同步问题。

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

  • 存在可通过其他字段计算得出的临时变量
  • 需要手动维护的派生状态
  • 多个变量之间存在计算依赖关系

用查询替换派生变量的详细步骤

  1. 识别派生变量
    • 查找所有仅通过其他字段计算得出的变量
    • 确认变量值没有外部修改入口
  2. 创建查询方法
    • 将变量计算逻辑封装为无参数方法
    • 确保方法没有副作用
  3. 替换变量引用
    • 将变量访问改为方法调用
    • 处理所有读写点
  4. 移除原变量
    • 删除变量声明和初始化代码
    • 清理关联的setter方法

示例

重构前代码:

class ShoppingCart {
    private List<Item> items = new ArrayList<>();
    private double total;

    public void addItem(Item item) {
        items.add(item);
        total += item.getPrice();
    }

    public void removeItem(Item item) {
        items.remove(item);
        total -= item.getPrice();
    }

    public double getTotal() {
        return total;
    }
}

重构步骤:

  1. 创建总价计算方法:

    public double calculateTotal() {
        return items.stream()
                .mapToDouble(Item::getPrice)
                .sum();
    }
    
  2. 替换total变量引用:

    class ShoppingCart {
        private List<Item> items = new ArrayList<>();
    
        public void addItem(Item item) {
            items.add(item);
        }
    
        public void removeItem(Item item) {
            items.remove(item);
        }
    
        public double getTotal() {
            return calculateTotal();
        }
    
        private double calculateTotal() {
            return items.stream()
                    .mapToDouble(Item::getPrice)
                    .sum();
        }
    }
    

练习

基础练习题

  1. 简单派生变量替换

    // 重构前
    class Rectangle {
        private int width;
        private int height;
        private int area;
    
        public Rectangle(int w, int h) {
            width = w;
            height = h;
            area = width * height;
        }
    
        public int getArea() {
            return area;
        }
    }
    

进阶练习题

  1. 动态计算替换

    // 重构前
    class Order {
        private List<Product> products;
        private double discount;
        private double total;
    
        public void applyDiscount(double rate) {
            discount = calculateSubtotal() * rate;
            total = calculateSubtotal() - discount;
        }
    
        private double calculateSubtotal() {
            return products.stream()
                    .mapToDouble(Product::getPrice)
                    .sum();
        }
    }
    

综合拓展练习题

  1. 多重依赖变量替换

    // 重构前
    class Student {
        private List<Course> courses;
        private double gpa;
        private boolean isHonor;
    
        public void updateStatus() {
            gpa = calculateGPA();
            isHonor = gpa >= 3.8;
        }
    
        private double calculateGPA() {
            return courses.stream()
                    .mapToDouble(Course::getScore)
                    .average()
                    .orElse(0.0);
        }
    }
    

代码审查要点

  1. 优点:

    • 消除状态同步问题
    • 简化对象生命周期管理
    • 提高代码可测试性
  2. 潜在问题:

    • 避免高频调用的性能损耗
    • 确保计算方法的幂等性
    • 处理并发环境下的计算一致性