我们在滥用静态方法吗?


13

几个月前,我开始从事一个新项目,在遍历代码时,它使我震惊了所使用的静态方法。它们不仅保留了实用程序方法collectionToCsvString(Collection<E> elements),而且还保留了大量业务逻辑。

当我问负责背后原因的那个人时,他说这是逃避斯普林暴政的一种方式。它围绕着这个思考过程进行了一些操作:要实现客户收据创建方法,我们可以提供服务

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

现在,那个家伙说他不喜欢让Spring不必要地管理类,主要是因为它强加了限制,即客户类必须是Spring bean本身。我们最终将一切都由Spring管理,这几乎迫使我们以过程方式使用无状态对象。或多或少在这里说了什么https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

因此,除了上面的代码,他有

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

我可以说要避免在可能的情况下避免Spring管理我们的类,但是我看不到使所有内容保持静态的好处。这些静态方法也是无状态的,所以也不是很OO。我会更喜欢

new CustomerReceiptCreator().createReceipt()

他声称静态方法有一些额外的好处。即:

  • 更容易阅读。导入静态方法,我们只需要关心该动作,而无需执行任何类。
  • 显然,它是一种无需进行DB调用的方法,因此在性能上很便宜;弄清楚是一件好事,以便潜在客户确实需要进入代码并进行检查。
  • 编写测试更容易。

但是我只是觉得有些不对劲,所以我想听听一些经验丰富的开发者对此的想法。

所以我的问题是,这种编程方式的潜在陷阱是什么?




4
static上面说明的方法只是普通的工厂方法。由于许多令人信服的原因,使工厂方法静态化是普遍接受的约定。这里的工厂方法是否合适是另一回事。
罗伯特·哈维

Answers:


23

new CustomerReceiptCreator().createReceipt()和之间有什么区别CustomerReceiptCreator.createReceipt()?几乎没有。唯一的显着区别是,第一种情况的语法更加笨拙。如果您遵循第一个信念,即以某种方式避免使用静态方法可以使您的代码更好地面向对象,那么您将严重地犯错误。通过钝化语法,创建一个对象以仅对其调用单个方法是静态方法。

当您注入CustomerReceiptCreator而不是注入时,事情确实有所不同new。让我们考虑一个例子:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

让我们比较一下静态方法版本:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

静态版本的优点是,我可以轻松地告诉您它与系统其余部分的交互方式。没有。如果我没有使用全局变量,那么我知道系统的其余部分都没有被更改。我知道系统的其他部分都不会影响此处的收据。如果我使用了不可变的对象,我知道顺序没有改变,并且createReceipt是一个纯函数。在这种情况下,我可以自由移动/删除/调用此电话,而不必担心其他地方的随机不可预测的影响。

如果注入,我无法做出相同的保证CustomerReceiptCreator。它的内部状态可能会因调用而更改,我可能会受到其他状态的影响或更改。我的函数中的语句之间可能存在不可预测的关系,因此更改顺序将引入令人惊讶的错误。

另一方面,如果CustomerReceiptCreator突然需要新的依赖关系会怎样?假设它需要检查功能标记。如果要进行注射,则可以执行以下操作:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

然后我们完成了,因为调用代码将被注入a CustomerReceiptCreator,该代码将被自动注入a FeatureFlags

如果我们使用静态方法怎么办?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

可是等等!调用代码也需要更新:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

当然,这仍然留下的,其中问题processOrder得到它FeatureFlags的。如果幸运的话,足迹到此结束,否则,穿过FeatureFlags的必要性会进一步推高。

这里需要权衡。静态方法需要显式传递依赖项,这会导致更多工作。注入的方法减少了工作量,但使依赖项隐式化,从而隐藏了代码,使代码难以推理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.