“静态”作为关于无状态的语义线索?


11

我最近在Java中进行了一个中型项目的重构,以返回并添加单元测试。当我意识到模拟单例和静态模型是多么痛苦时,我终于“明白了”我一直以来一直在阅读的内容。(我是需要从经验中学习的人之一。哦。)

因此,既然我正在使用Spring创建对象并将它们连接在一起,那么我就不用去左右static关键字了。(如果我有可能要模拟它,那么它在与Math.abs()相同的意义上并不是真正的静态,对吧?)问题是,我已经习惯于使用static来表示方法不依赖在任何对象状态下。例如:

//Before
import com.thirdparty.ThirdPartyLibrary.Thingy;
public class ThirdPartyLibraryWrapper {
    public static Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
ThirdPartyLibraryWrapper.newThingy(input);
//After
public class ThirdPartyFactory {
    public Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
thirdPartyFactoryInstance.newThingy(input);

因此,这就是敏感的地方。我喜欢旧方法,因为大写字母告诉我,就像Math.sin(x)一样,ThirdPartyLibraryWrapper.newThingy(x)每次都以相同的方式执行相同的操作。没有对象状态可以更改对象执行我要执行的操作的方式。这是我正在考虑的一些可能的答案。

  • 没有其他人有这种感觉,所以我有问题。也许我只是还没有真正内部化OO的做事方式!也许我在用Java编写,但在FORTRAN或类似语言中思考。(由于我从未写过FORTRAN,这会给人留下深刻的印象。)
  • 也许出于论证代码的目的,我正在使用静态作为不变性的一种代理。话虽这么说,有什么线索 我在我的人一起,以保持它的到来代码知道什么是有状态的,什么不是?
  • 如果我选择好的对象隐喻,也许应该只是免费提供?例如thingyWrapper,听起来好像它的包装状态是独立的,而包装状态Thingy本身可能是可变的。同样,thingyFactory听起来应该是不变的,但在创建时可能会选择不同的策略。

Answers:


12

我喜欢旧方法,因为大写字母告诉我,就像Math.sin(x)一样,ThirdPartyLibraryWrapper.newThingy(x)每次都以相同的方式执行相同的操作。没有对象状态可以更改对象执行我要执行的操作的方式。

是的,这是正确的方法。也就是说,static不能保证不变性或状态持久性。您将其用作此类担保的建议确实是有道理的,但绝不能保证。

考虑以下:

public static class Logger
{
    public static int LogLevel { get; set; }

    public static void Log(string message, int logLevel)
    {
        if (logLevel >= LogLevel)
        {
            // logs the message, but only if it is important enough.
        }
    }
}

此类不仅保存状态,而且Logger.Log()更改状态时方法的行为也会更改。这是对static类模式的完全合法的练习,但是没有您所建议的语义保证。


我给您打了勾号,因为您对我的直觉基础提出了一个有益的反驳,但是我仍然想让您思考一下我应该如何通过编码来表示关于缺乏状态和没有副作用的契约/期望约定。
leoger

1
通常,这种期望包含在该方法的文档中。线程安全也是如此。
罗伯特·哈维

5

我认为您所说的-所有静态函数都为null-在大多数情况下是一种很好的面向对象的编写代码的方式,但是我不认为当您查看静态函数时,应该自动假定它没有副作用。 。情况可能更复杂;以Class.forName(String)看似无状态的函数为例,但是它实际上将一个类加载到内存中,并最终实例化静态字段/运行静态初始化程序;这是幂等函数的一个示例(第一个函数之后的最终调用没有区别),但它不是纯函数(不能说它没有副作用)。这也是一个不错的选择,但是在某些情况下,对同一函数的三个不同的调用可能会产生三个不同的结果。例如,如果您打电话Thread.currentThread() 在多线程应用程序中,不能保证您每次都会收到相同的线程。

话虽这么说,我应该在代码中提供什么线索,以便有人继续维护它以了解有状态和无状态?

适当的解决方案是记录文档(例如Javadoc);同样,从函数的名称以及它的功能有时可以推断出静态函数是一个纯函数。例如,没有理由让人们相信Assert.assertTrue(boolean)JUnit会改变某处的状态。另一方面,当调用like之类的函数时System.clearProperty(String),很明显会有副作用。


“营养不良”和“无状态”之间有什么区别?
Pacerier

@Pacerier不应有任何区别。
m3th0dman

4

话虽这么说,我应该在代码中提供什么线索,以便有人继续维护它以了解有状态和无状态?

您可以将它们设为静态。C#世界中的FxCop将推动您使没有引用成员变量的事物变为静态。这是正确的做法(通常)。

当我意识到模拟单例和静力学是多么痛苦时,我终于“明白了”我一直以来在阅读的内容。

将它们全部移至实例也许不是正确的方法。而是将静态方法的依赖项与使用它们的类分离。您可以隔离测试静态方法,然后在需要测试使用者时替换其依赖关系。


您能给我画一个“解耦静态方法的依赖关系”的例子吗?我不清楚您正在激活什么。我已经在静态方法中隐藏了实现细节。归根结底,使用它的类需要实现该功能。您是在说我应该为所有静态方法创建一个具有薄包装器的实例对象吗?
leoger

1
@leoger-如果要测试静态类,则使用静态方法的类无需实现功能,否则就不会在模拟静态方法。
Telastyn
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.