45、代码坏味道:依恋情结 - 实战搬移函数与策略模式

今天我们来聊一个很常见的坏味道——依恋情结

说白了,就是一个类太“粘”另一个类了。它老是去访问别人的数据,调用别人的方法,搞得自己像个“跟屁虫”。我见过不少项目,代码写着写着,一个类里全是别的类的getter调用,自己的逻辑反而没多少。

为什么会这样?

因为人都有惰性。你想想看,写一个新功能时,发现某个类里已经有现成的数据,顺手就调过来了。一次两次还好,时间长了,这个类就变成了“数据中转站”,而那个被依赖的类则越来越臃肿。

嗯,今天我们就用两个实战案例,把这个问题彻底讲透。

什么是依恋情结?

依恋情结(Feature Envy)的定义很简单:一个方法对另一个类的兴趣,超过了对自身所在类的兴趣

判断标准也很直观:

  • 方法里大量调用其他类的getter
  • 方法里频繁访问其他类的属性
  • 方法里多次调用其他类的方法,而自己类的成员几乎没用

我个人的习惯是:如果一个方法里,超过一半的代码都在跟别的类“打交道”,那就要警惕了

核心原则:把方法搬移到它真正“依恋”的那个类里去。

案例一:搬移函数

先看一个简单的例子。假设我们有一个订单系统:

public class Order {
    private Customer customer;
    private double amount;

    public double calculateDiscount() {
        // 这个方法大量依赖 Customer 类的数据
        if (customer.isVIP()) {
            return amount * 0.2;
        } else if (customer.getTotalOrders() > 10) {
            return amount * 0.1;
        } else {
            return amount * 0.05;
        }
    }
}

你看,calculateDiscount 方法里,几乎每一行都在调用 customer 的东西。它依恋的是 Customer 类,而不是 Order 类。

怎么改?搬移函数。

public class Customer {
    private boolean vip;
    private int totalOrders;

    public double calculateDiscount(double amount) {
        if (isVIP()) {
            return amount * 0.2;
        } else if (getTotalOrders() > 10) {
            return amount * 0.1;
        } else {
            return amount * 0.05;
        }
    }
}

public class Order {
    private Customer customer;
    private double amount;

    public double calculateDiscount() {
        return customer.calculateDiscount(amount);
    }
}

改完之后,OrdercalculateDiscount 只剩一行委托调用。逻辑归属清晰了,Customer 自己决定怎么打折,而不是让 Order 去猜。

小技巧:搬移函数时,如果原方法里用到了当前类的数据,就把这些数据作为参数传过去。就像上面把 amount 传给了 Customer

案例二:策略模式

有时候,依恋情结没那么简单。一个方法可能依恋多个不同的类,或者依恋的逻辑太复杂,单纯搬移函数解决不了。

我曾经在一个支付系统里遇到过这种情况。一个 PaymentService 类里,有个 calculateFee 方法,它要判断支付方式(微信、支付宝、银行卡),然后去不同的类里拿费率数据。代码长得吓人,全是 if-else 和 getter 调用。

这就是典型的依恋情结——PaymentService 太“粘”那些支付方式类了。

这时候,策略模式就派上用场了。

先定义一个策略接口:

public interface PaymentStrategy {
    double calculateFee(double amount);
}

然后每个支付方式实现自己的策略:

public class WeChatPayStrategy implements PaymentStrategy {
    @Override
    public double calculateFee(double amount) {
        // 微信的费率逻辑
        return amount * 0.006;
    }
}

public class AlipayStrategy implements PaymentStrategy {
    @Override
    public double calculateFee(double amount) {
        // 支付宝的费率逻辑
        return amount * 0.0055;
    }
}

public class BankCardStrategy implements PaymentStrategy {
    @Override
    public double calculateFee(double amount) {
        // 银行卡的费率逻辑
        return amount * 0.008;
    }
}

最后,PaymentService 只需要持有策略对象:

public class PaymentService {
    private PaymentStrategy strategy;

    public PaymentService(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculateFee(double amount) {
        return strategy.calculateFee(amount);
    }
}

你看,PaymentService 不再依恋任何具体的支付方式类。它只依赖一个抽象的 PaymentStrategy 接口。每个策略类自己管理自己的费率逻辑,互不干扰。

注意:策略模式不是万能的。如果只有两三种简单的分支判断,用 if-else 反而更清晰。别为了用模式而用模式。

知识体系图

下面这张图帮你理清依恋情结的解决思路:

依恋情结解决思路 依恋情结 判断:依恋程度有多深? 搬移函数 策略模式 方法逻辑搬移到被依恋的类 原类只保留委托调用 定义策略接口,各实现独立 上下文只依赖抽象接口

什么时候用搬移函数,什么时候用策略模式?

我个人的经验是:

场景 推荐方案 原因
方法只依恋一个类 搬移函数 简单直接,改动最小
方法依恋多个类,且逻辑相似 策略模式 消除重复,便于扩展
依恋逻辑未来可能频繁变化 策略模式 开闭原则,新增策略不影响现有代码
依恋逻辑只有两三种固定分支 搬移函数 + 简单判断 过度设计反而增加复杂度

避坑指南

我曾经在一个项目里,看到同事把整个 Order 类的方法全搬到了 Customer 里。结果 Customer 变得巨大无比,什么订单逻辑、折扣逻辑、物流逻辑全塞进去了。

这就是矫枉过正。

搬移函数不是“把所有东西都搬过去”,而是把真正属于那个类的逻辑搬过去。如果一个方法既用了 Customer 的数据,又用了 Order 的数据,那它可能属于一个独立的第三个类,或者应该用策略模式来拆分。

我的建议:每次搬移前,先问自己一个问题——“这个方法如果放在目标类里,读起来是不是更自然?” 如果答案是肯定的,那就搬。如果犹豫,那就再想想。

总结

依恋情结这个坏味道,说白了就是“代码放错了地方”。

解决它有两个武器:

  • 搬移函数:适合简单的一对一依恋
  • 策略模式:适合复杂的多对多依恋

记住一个原则:让数据和处理数据的逻辑待在一起。这样代码才会干净、好维护。

嗯,今天就聊到这里。下次你看到某个方法里全是别的类的 getter,别犹豫,动手重构吧。

公众号:蓝海资料掘金营,微信 deep3321