重构手法——数据重组类 | 数据关系重构 | 移动方法

76 阅读6分钟

简介

“移动方法”(Move Method)是一种常用的重构手法,通过将一个方法从一个类移动到另一个类中,可以提高代码的组织性和可维护性。这种方法特别适用于当一个方法与其所在类的职责不匹配,或者与另一个类的职责更相关时。以下是进行“移动方法”重构的详细步骤:

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

  • 方法与其所在类的职责不匹配(Feature Envy)
  • 方法频繁访问另一个类的数据(Inappropriate Intimacy)
  • 类之间的职责分配不合理(Poor Class Responsibility Assignment)

移动方法(Move Method)的详细步骤

  1. 识别需要移动的方法
    • 寻找职责不匹配的方法:在代码中找到与其所在类的职责不匹配的方法。
    • 识别频繁访问其他类数据的方法:找到频繁访问另一个类的数据的方法。
    • 评估方法的独立性:确保移动的方法具有一定的独立性和完整性,可以作为一个独立的方法存在。
  2. 选择目标类
    • 选择合适的目标类:选择一个与移动方法职责更相关的类作为目标类。
    • 确保目标类的可访问性:确保目标类能够访问移动方法所需的数据和方法。
  3. 移动方法
    • 复制方法到目标类:将选定的方法复制到目标类中。
    • 调整方法签名:根据目标类的上下文调整方法的签名,包括参数和返回值。
    • 传递必要的参数:确保目标类的方法能够接收必要的参数,以完成其功能。
    • 返回值:如果需要,确保目标类的方法能够返回必要的值。
  4. 替换原始代码
    • 调用新方法:在原始类中,用调用新方法的语句替换原始方法的调用。
    • 清理未使用的代码:删除原始类中不再需要的方法或代码。
  5. 测试
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
    • 手动测试:如果有必要,进行手动测试以验证功能的正确性。
  6. 代码审查
    • 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
    • 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明移动方法的影响。

示例

假设有一个类 Order,其中包含一个方法 calculateShippingCost,该方法与 Shipping 类的职责更相关,我们希望对其进行“移动方法”的重构:

public class Order {
   private double price;
   private int quantity;
   private double taxRate;
   private double shippingCost;

   public double calculateTotal() {
      double subTotal = price * quantity;
      double tax = subTotal * taxRate;
      double finalTotal = subTotal + tax;
      if (quantity > 10) {
         finalTotal += calculateShippingCost();
      }
      return finalTotal;
   }

   private double calculateShippingCost() {
      return shippingCost * quantity;
   }
}

public class Shipping {
   private double shippingCost;

   public Shipping(double shippingCost) {
      this.shippingCost = shippingCost;
   }

   public double calculateShippingCost(int quantity) {
      return shippingCost * quantity;
   }
}

步骤如下:

  1. 识别需要移动的方法:
    • 职责不匹配的方法: calculateShippingCost 方法与 Order 类的职责不匹配,与 Shipping 类的职责更相关。
  2. 选择目标类:
    • 选择合适的目标类: Shipping 类。
  3. 移动方法:
    • 复制方法到目标类:将 calculateShippingCost 方法复制到 Shipping 类中。
    • 调整方法签名:根据 Shipping 类的上下文调整方法的签名,包括参数和返回值。
    • 传递必要的参数:确保 Shipping 类的方法能够接收必要的参数,以完成其功能。
    • 返回值:确保 Shipping 类的方法能够返回必要的值。
  4. 替换原始代码:
    • Order 类中,用调用新方法的语句替换原始方法的调用:

        public class Order {
         private double price;
         private int quantity;
         private double taxRate;
         private Shipping shipping;
      
         public Order(double price, int quantity, double taxRate, Shipping shipping) {
            this.price = price;
            this.quantity = quantity;
            this.taxRate = taxRate;
            this.shipping = shipping;
         }
      
         public double calculateTotal() {
            double subTotal = price * quantity;
            double tax = subTotal * taxRate;
            double finalTotal = subTotal + tax;
            if (quantity > 10) {
               finalTotal += shipping.calculateShippingCost(quantity);
            }
            return finalTotal;
         }
      }
      
  5. 测试:
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
  6. 代码审查:
    • 让同事审查代码,确保没有引入新的问题。

练习

基础练习题

  1. 移动方法与职责匹配
    • 给定以下 Java 代码,Customer 类中的 calculateDiscount 方法与 Customer 类的职责不匹配,与 Discount 类的职责更相关。请将 calculateDiscount 方法移动到 Discount 类中。

        public class Customer {
         private String name;
         private double totalPurchases;
         private Discount discount;
      
         public Customer(String name, double totalPurchases, Discount discount) {
            this.name = name;
            this.totalPurchases = totalPurchases;
            this.discount = discount;
         }
      
         public double calculateTotal() {
            return totalPurchases - discount.calculateDiscount(totalPurchases);
         }
      }
      
      public class Discount {
         private double discountRate;
      
         public Discount(double discountRate) {
            this.discountRate = discountRate;
         }
      
         public double calculateDiscount(double totalPurchases) {
            return totalPurchases * discountRate;
         }
      }
      
  2. 移动方法与参数传递
    • 下面的 Java 代码中,Order 类中的 calculateTax 方法与 Order 类的职责不匹配,与 Tax 类的职责更相关。请将 calculateTax 方法移动到 Tax 类中,并正确处理参数传递。

        public class Order {
         private double price;
         private int quantity;
         private Tax tax;
      
         public Order(double price, int quantity, Tax tax) {
            this.price = price;
            this.quantity = quantity;
            this.tax = tax;
         }
      
         public double calculateTotal() {
            return price * quantity + tax.calculateTax(price * quantity);
         }
      }
      
      public class Tax {
         private double taxRate;
      
         public Tax(double taxRate) {
            this.taxRate = taxRate;
         }
      
         public double calculateTax(double amount) {
            return amount * taxRate;
         }
      }
      

进阶练习题

  1. 复杂逻辑块移动与参数传递
    • 在这段 Java 代码中,Order 类中的 calculateShippingCost 方法与 Order 类的职责不匹配,与 Shipping 类的职责更相关。请将 calculateShippingCost 方法移动到 Shipping 类中,并正确处理参数传递。

        public class Order {
         private double price;
         private int quantity;
         private Shipping shipping;
      
         public Order(double price, int quantity, Shipping shipping) {
            this.price = price;
            this.quantity = quantity;
            this.shipping = shipping;
         }
      
         public double calculateTotal() {
            return price * quantity + shipping.calculateShippingCost(quantity);
         }
      }
      
      public class Shipping {
         private double baseShippingCost;
         private double discountRate;
      
         public Shipping(double baseShippingCost, double discountRate) {
            this.baseShippingCost = baseShippingCost;
            this.discountRate = discountRate;
         }
      
         public double calculateShippingCost(int quantity) {
            double shippingCost = baseShippingCost * quantity;
            return shippingCost - (shippingCost * discountRate);
         }
      }
      
  2. 移动方法与返回值处理
    • 给定以下 Java 代码,Order 类中的 calculateTotal 方法包含复杂逻辑。请将计算总价的部分提取到 OrderCalculator 类中,并确保返回值正确处理。

        public class Order {
         private double price;
         private int quantity;
         private double taxRate;
         private double shippingCost;
         private OrderCalculator orderCalculator;
      
         public Order(double price, int quantity, double taxRate, double shippingCost, OrderCalculator orderCalculator) {
            this.price = price;
            this.quantity = quantity;
            this.taxRate = taxRate;
            this.shippingCost = shippingCost;
            this.orderCalculator = orderCalculator;
         }
      
         public double calculateTotal() {
            return orderCalculator.calculateTotal(price, quantity, taxRate, shippingCost);
         }
      }
      
      public class OrderCalculator {
         public double calculateTotal(double price, int quantity, double taxRate, double shippingCost) {
            double subTotal = price * quantity;
            double tax = subTotal * taxRate;
            double finalTotal = subTotal + tax;
            if (quantity > 10) {
               finalTotal += shippingCost;
            }
            return finalTotal;
         }
      }
      

综合拓展练习题

  1. 多模块移动与代码审查模拟
    • 考虑一个简单的 Java 电商系统,有 Product 类、Cart 类和 Order 类。Cart 类中的 calculateCartTotal 方法和 Order 类中的 calculateOrderTotal 方法都有重复且复杂的计算总价逻辑,同时 Cart 类中的 applyCartDiscount 方法逻辑也较为复杂。
    • 请对这些方法进行 “移动方法” 重构,将重复和复杂逻辑移动到 OrderCalculator 类中。
    • 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。
        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;
         private OrderCalculator orderCalculator;
      
         public Cart(Product[] products, double discountRate, OrderCalculator orderCalculator) {
            this.products = products;
            this.discountRate = discountRate;
            this.orderCalculator = orderCalculator;
         }
      
         public double calculateCartTotal() {
            return orderCalculator.calculateCartTotal(products, discountRate);
         }
      
         public void applyCartDiscount() {
            if (products.length > 3) {
               discountRate = orderCalculator.adjustDiscountRate(discountRate, 0.05);
            }
            if (calculateCartTotal() > 100) {
               discountRate = orderCalculator.adjustDiscountRate(discountRate, 0.1);
            }
         }
      }
      
      class Order {
         private Product[] products;
         private double shippingCost;
         private OrderCalculator orderCalculator;
      
         public Order(Product[] products, double shippingCost, OrderCalculator orderCalculator) {
            this.products = products;
            this.shippingCost = shippingCost;
            this.orderCalculator = orderCalculator;
         }
      
         public double calculateOrderTotal() {
            return orderCalculator.calculateOrderTotal(products, shippingCost);
         }
      }
      
      class OrderCalculator {
         public double calculateCartTotal(Product[] products, double discountRate) {
            double total = calculateSubtotal(products);
            return applyDiscount(total, discountRate);
         }
      
         public double calculateOrderTotal(Product[] products, double shippingCost) {
            double total = calculateSubtotal(products);
            return total + shippingCost;
         }
      
         private double calculateSubtotal(Product[] products) {
            double total = 0;
            for (Product product : products) {
               total += product.getPrice();
            }
            return total;
         }
      
         private double applyDiscount(double total, double rate) {
            double discountAmount = total * rate;
            return total - discountAmount;
         }
      
         public double adjustDiscountRate(double rate, double increment) {
            return rate + increment;
         }
      }