Java-拥有完全静态的类是一个坏主意吗?


16

我正在一个更大的单独项目中工作,并且现在有几个类,在这些类中我看不到创建其实例的任何理由。

例如,我现在的骰子类静态地存储其所有数据,并且其所有方法也都是静态的。我不需要初始化它,因为当我想掷骰子并获得新值时,我只是使用Dice.roll()

我有几个类似的类,它们只有一个这样的主要功能,我将开始研究一种“控制器”类,它将负责所有事件(例如,玩家移动时以及当前的转弯方向)是的),而且我发现我可以针对这一堂课遵循相同的想法。我从来没有打算为这些特定的类创建多个对象,因此使它们完全静态将是一个坏主意吗?

我想知道当涉及到Java时,这是否被视为“不好的做法”。从我所看到的,社区似乎在这个话题上有些分歧?无论如何,我希望对此进行一些讨论,并且与资源的链接也很棒!


1
如果您的程序是完全程序性的。为什么选择Java?
Laiv

12
尝试为这些类编写单元测试,您将发现为什么人们不喜欢访问静态状态的静态方法。
Joeri Sebrechts

@Laiv我还是编程的新手,大约一年的C ++工作,本学期我将学习Java类,并且我开始更喜欢Java,尤其是图形库。
HexTeke


5
关于静态类。如果静态方法是纯方法(不持有任何状态,则具有输入参数和返回类型)。无需担心
Laiv

Answers:


20

真正是静态的静态类没有错。也就是说,没有可以说会导致方法输出更改的内部状态。

如果Dice.roll()只是从1到6返回一个新的随机数,则不会更改状态。当然,您可能正在共享一个Random实例,但是根据定义,我不会认为状态会发生变化,输出始终会是随机的。它也是线程安全的,因此这里没有问题。

您经常会看到最终的“ Helper”或其他具有私有构造函数和静态成员的实用程序类。私有构造函数不包含任何逻辑,仅用于防止某人实例化该类。最后的修饰符只会使这个主意变为现实,因为这不是您想要派生的类。它仅仅是一个实用程序类。如果做得正确,则不应有单例或其他本身不是静态的和最终的类成员。

只要您遵循这些准则,并且您没有使单身,这绝对没有错。您提到了一个控制器类,并且几乎肯定会需要状态更改,因此我建议不要只使用静态方法。您可以高度依赖静态实用程序类,但不能使其成为静态实用程序类。


什么是班级状态变化?好吧,让我们排除一秒钟的随机数,因为根据定义它们是不确定的,因此返回值经常变化。

纯函数是确定性函数,也就是说,对于给定的输入,您将只获得一个输出。您希望静态方法是纯函数。在Java中,有一些方法可以调整静态方法的行为以保持状态,但它们绝不是好主意。当您将一个方法声明为static时,典型的程序员会马上认为它是一个纯函数。通常,应该避免与预期行为的偏离,这是您倾向于在程序中创建错误的方式。

单例是一个包含静态方法的类,它与“纯函数”差不多。该类内部仅保留一个静态私有成员,该成员用于确保仅存在一个实例。这不是最佳做法,并且由于多种原因可能在以后给您带来麻烦。要知道我们在说什么,下面是一个单例的简单示例:

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"

7
如果我想测试两次翻倍会发生什么情况,那么我会停留在这里,因为您有一个带有静态随机数生成器的静态类。因此,不,Dice.roll()这不是“无全局状态”规则的有效例外。
David Arno

1
@HexTeke更新了答案。
尼尔

1
@DavidArno是的,但是我想如果要测试对的单个调用,我们真的很麻烦random.nextInt(6) + 1。;)
尼尔

3
@Neil,抱歉,我对自己的解释不是很好。我们不会测试随机数序列,而是会影响该序列以协助其他测试。如果我们正在测试,例如RollAndMove(),当我们提供双六时再次掷骰子,那么最简单,最可靠的方法是模拟掉Dice随机数生成器或随机数生成器。恩,Dice不想使用静态随机生成器成为静态类。
David Arno

12
但是,您的更新首先引起了问题:静态方法应该是确定性的;他们应该没有副作用。
David Arno

9

举例说明一static类游戏的局限性,如果您的某些游戏玩家想从自己的掷骰子中获得一点奖励呢?他们愿意为此付出高昂的代价!:-)

是的,您可以添加另一个参数,因此Dice.roll(bonus)

以后您需要D20。

Dice.roll(bonus, sides)

是的,但是有些玩家拥有“至高无上”的壮举,因此他们永远都不会“摸索”(掷1)。

Dice.roll(bonus, sides, isFumbleAllowed)

这越来越混乱了,不是吗?


这似乎是正交的问题,它变得杂乱这些是static方法或常规方法
JK。

4
@jk,我想我明白他的意思。当您考虑更多类型的骰子时,没有Dice静态类是没有意义的。在这种情况下,我们可以拥有不同的骰子对象,并通过良好的OOP做法对其进行建模。
Dherik

1
@TimothyTruckle我没有争议,我只是说这个答案实际上并没有回答问题,因为这种范围爬升与方法是否静态无关。
nvoigt

2
@nvoigt “我没有争议” -好吧,我知道。OO语言最强大的功能是多态性。与静态接入有效地阻止我们使用,在所有。而且您是对的:new Dice().roll(...)必须考虑静态访问以及Dice.roll(...)。只有注入这种依赖性,我们才能受益。
Timothy Truckle '18

2
@jk的不同之处在于,每次调用都会static发生混乱,并且每次调用都需要所需的知识,因此它会遍及您的应用程序。在OOP结构中,只需要使构造函数/工厂杂乱无章,并封装该裸片的详细信息。之后,使用多态并调用roll()。我可能会编辑此答案以澄清。
user949300'4

3

我认为在Dice类的特定情况下,使用实例方法而非静态方法将使测试变得更加容易。

如果您想对使用Dice实例的某个东西进行单元测试(例如Game类),则可以从测试中注入Dice某种形式的测试double,它总是返回固定的值序列。您的测试可以检查游戏对于那些掷骰子是否具有正确的结果。

我不是Java开发人员,但我认为使用完全静态的Dice类很难做到这一点。参见/programming/4482315/why-does-mockito-not-mock-static-methods


仅作记录:在Java中,我们有PowerMock通过操作字节码来替换静态依赖项。但是使用它只是屈服于糟糕的设计……
Timothy Truckle '18

0

这实际上称为Monostate模式,其中每个实例(甚至是“无实例”)都共享其状态。每个成员都是一个类成员(即没有实例成员)。它们通常用于实现“工具箱”类,这些类捆绑了与单个职责或要求相关的一组方法和常量,但不需要状态就可以工作(它们纯粹是功能性的)。实际上,Java捆绑了其中的一些功能(例如Math)。

有点题外话:我很少同意VisualBasic中关键字的命名,但是在这种情况下,我认为shared(在类本身及其所有实例之间共享)肯定比static(在生命周期之后仍然存在)更清晰和语义上更好。声明的范围)。

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.