【问题标题】:What is the best way to refactor Utility class in java (static classes)在java(静态类)中重构Utility类的最佳方法是什么
【发布时间】:2015-08-10 21:35:14
【问题描述】:

我正在考虑重构我们的一些实用程序类(静态类)。 静态类很难测试,主要问题是它的制作 我们的代码耦合非常紧密,依赖很多。 用于重构的最佳设计模式是什么? 我考虑过使用构建器的不可变对象,但我不确定

将此代码视为 1 我要重构

public class UtilTest {

    public static boolean  isEligibleItem(Item item){
         if(isCondition1(item)){
             return isCondition2(item);
         }

         return false;
    }

    public static  boolean  isCondition1(Item item){
        //go to service that go to the data base  
        return false;
    }

    public static boolean  isCondition2(Item item){
        //go to service that go to the data base  
        return false;
    }
}

如果我想测试我的 isEligibleItem() 方法,我需要模拟去 db 的 2 方法。 我不能这样做,因为它们是静态的。我想避免使用 Powermock

【问题讨论】:

  • 也许举个例子会有用。你能告诉我们吗?
  • 有时你想模拟他们,他们这样做可能很麻烦。
  • 你能显示一些代码吗?如果不知道代码在做什么,很难提供响应
  • 没有具体的代码讨论,这个问题不清楚或太宽泛。如果您确实有具体的代码要讨论,那么您最好在Code Review 上提问。
  • 与流行的看法相反,静态方法很容易测试。另一方面,调用静态方法的方法......不是。

标签: java oop design-patterns immutability


【解决方案1】:

实用程序类反模式

人们说静态方法很难测试的原因更多是因为它如何随着时间的推移将不相关的类紧密耦合降低凝聚力 以及引入 i不可见的副作用这三件事比一些单元测试挥手抱怨更重要

测试交互

更多的是测试与其他代码的交互,而不是测试static 方法本身。这就是 Java 真正需要 Functions 作为第一类对象开始的地方。

在大多数情况下,只有static 方法的类绝对是代码异味。也有例外,但这种反模式往往会被非面向对象语言的初学者和老手滥用。

规则的例外 - 不可变

例外情况主要是标记为final 的类中可能被视为缺失的事物,例如String,即Immutable

拥有一个泛化了static 方法的Strings 类并不是那么糟糕,因为String 是不可变的(没有副作用),并且您不能向String 类添加任何内容,因此您没有很多选择. Integer 等也是如此,Guava 具有这种命名约定,适用于这些不可变对象。

副作用

static 方法往往会引入很多副作用。以某种不透明的方式获取一个对象并操纵该对象的事情是不好的,更糟糕​​的是,当它们查找其他对象并根据传入的实例也操纵它们时,它们会混淆正在发生的事情并且紧密耦合并且内聚力低。

高内聚

Tight Cohesion 没有像 Coupling 那样被广泛讨论,但它同样重要。它们是同一枚硬币的两个面,忽略一个会导致另一个受苦。

这些static 方法应该在它们作为参数的类上,它们与这些类紧密耦合。在这种情况下,为什么他们不在Item 类上?

一旦您添加另一个采用SomeOtherItemstatic 方法,您就会将不相关的类间接耦合在一起。

解决此问题的最简单方法是将事物移到更接近在这种情况下属于 Item 类的位置。

工厂/提供者模式

如果你的东西确实是general 或者因为它是final 或其他原因而无法添加到类中的东西,使用接口和Provider 模式是使用@987654342 的最佳方法@ 生成 Provider 实例更好。

然后您可以使用Guice 之类的东西来注入您需要的任何实现,具体取决于它是否是测试。

甚至还有一个混合的Utility 模式,它可以从Provider 注入实现,这将为您提供static 方法的便利性以及没有它的灵活性和可维护性。

【讨论】:

    【解决方案2】:

    一个更可测试的设置的简单翻译是:

    public class UtilTest {
    
        private final MyDatabaseService service;
    
        public UtilTest(MyDatabaseService service) {
           this.service = service;
        }
    
        public boolean  isEligibleItem(Item item){
             if(isCondition1(item)){
                 return isCondition2(item);
             }
             return false;
        }
    
        public boolean isCondition1(Item item){
            this.service.goToDataBase();
            return false;
        }
    
        public boolean isCondition2(Item item){
            this.service.goToDataBase2();
            return false;
        }
    }
    

    这并不能消除所有问题,但这是一个开始,您可以使用模拟的数据库服务来测试您的课程。

    如果你想更进一步,你可以声明一个接口,其中包含你希望UtilTest 公开的所有方法(你可能还想重命名类......),并让UtilTest 实现它。所有使用UtilTest的代码都应该重写为使用接口,然后你就可以完全直接地模拟UtilTest。这是否值得在很大程度上取决于UtilTest 在现实中的复杂程度。如果它执行的任务相对简单,您可能会认为它比它的价值更麻烦。但是,如果那里有一些繁重的处理,您肯定希望使其易于模拟。

    【讨论】:

    • 是的,这是一个好的开始(依赖注入)。但是我们的 utils 类依赖于其他服务,我不希望我的构造函数太大。这就是为什么我考虑不可变和构建器的原因,因为我可以构建我想要的,然后运行它并测试它
    • @MosheTsabari 似乎这些实用程序类根本不是实用程序类。实用程序类处理非业务逻辑类型代码。如果您正在连接服务,那么它可能不是一个实用程序类,应该被纳入处理这些服务的对象中。
    • @JoseMartinez 或者他们应该自己变成更高级别的服务。
    • 这是我正在处理的问题。一些开发人员想要创建方法并在不创建实例的情况下使用它们。所以他们将它们推送到他们找到的最有可能的实用程序类,现在我们需要重构所有..
    • @MosheTsabari 你应该说服他们不要这样做。如果您在说服他们时遇到问题,请向martialarts.stackexchange.com 提问:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 2014-12-11
    相关资源
    最近更新 更多