不可变类的静态方法 vs 非静态方法

8

考虑下面的类定义。如何决定存根方法是静态的还是非静态的?

class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // Should the methods add(), subtract() and inverseOf() be non-static ...

    public Point add(Point point) {

    }

    public Point subtract(Point point) {

    }

    public Point inverseOf() {

    }


    // Or static?

    public static Point add(Point point1, Point point2) {

    }

    public static Point subtract(Point point1, Point point2) {

    }

    public static Point inverseOf(Point point) {

    }
}

不,这不是作业。我只是在努力确定我的应用程序中某些方法应该是静态的还是非静态的。我使用了一个琐碎的例子,以确保我的应用程序的具体细节不会分散注意力,重点是静态与非静态。 - user63904
我肯定会将那个类声明为final。同时,将构造函数设为private并添加一个静态的创建方法也是一个好主意。 - Tom Hawtin - tackline
11个回答

9

我会选择实例方法。这样,您就可以将方法作为接口的一部分并覆盖它们。当您需要处理2D点或3D点并且有一些客户端代码只需要对实现接口的点执行操作时,您将获得好处。


我刚想打同样的答案。你更快,所以+1 :-) - Mendelt
3
仅当您需要进行大量调用而空指针实例有效时,才需要使用静态方法。 - james

3

我认为这取决于你想要实现什么。如果你提供了一种将任意两个点相加的方法,那么你需要一个静态方法。但是,如果你想要一个将点添加到给定点实例的方法,则需要一个非静态方法。

如果你使用静态方法,那么可以考虑将静态方法放入一个单独的实用程序类(PointCalculator)中,该类仅包含静态方法。这类似于Math类。


考虑到这个类是不可变的,我不明白“提供一个方法来添加任意两个点”和“为给定的点实例添加一个点的方法”之间的区别在哪里。 - user63904
一个静态方法表示你正在添加两个点以创建一个新的点。一个非静态方法表示你正在修改现有的点。当然这并不是真的,因为你正在返回一个新的点。例如,用户可以写 p1.add(p2);(用户可能认为他们已经将p2添加到p1中,并且新值在p1中)而不是Point p3=p1.add(p2),但是Point p3=Point.add(p1, p2)非常清晰。所以这是静态方法的一个优点。 - richs

3
我建议使用非静态方法,这更符合面向对象的思想(是的,使用过多静态方法会破坏对象的好处,如多态、继承...),即使您的Point是不可变的。实际上,这与类如BigDecimalBigInteger设计的方式一致。此外,静态方法会使类更难测试,因此我尽可能避免使用它们,特别是在有意义的时候。

2

从语义上看,静态方法似乎更有意义。两种方法当然都可以使用,但非静态方法会优先考虑一个点,而且暗示着点1(调用add方法的方法)可能会因为调用而被修改。

作为使用你的类的开发人员,如果我看到以下内容:

Point p1 = new Point(1,2);
Point p2 = new Point(2,3);

p1.Add(p2);

或者...
Point p1 = new Point(1,2);
Point p2 = new Point(2,3);

Point.Add(p1, p2);

我自然而然的倾向于认为非静态版本中的add()方法会修改point1以添加point2的结果。使用静态方式时,它更清晰(尽管不保证!)该方法是纯的,代表点不被修改。


采用非静态方法但将名称更改为加号和减号可能是一个不错的折中方案。 - Geoff Reedy

1

当方法的主体不依赖于任何特定实例时,请使用静态方法。

举个例子,看看您的add(Point, Point)方法。 您正在将传递给函数作为参数的两个Point相加,并返回另一个Point。 这真的需要内部this引用某个Point吗?

另一方面,您有一个add(Point)方法。 大概这会将函数参数添加到实例中-在这种情况下,您必须将其设置为实例方法,以便您拥有两个Point

编辑:我原来想法误解了。 回头看,您具有静态和非静态实现的正确签名。 此时,我会说这是一个样式问题,因为您知道两者都可以正常工作。 您希望如何使用您的点类? 想想是否更直观地说Point a = Point.add(b,c)还是Point a = b.add(c)使代码更易于理解。 就我个人而言,我喜欢前者,因为它告诉我,没有一个运算数将被修改。


给那位点踩的用户:通常最好留下一条评论来解释为什么要点踩。 - danben
点赞了,因为我讨厌那些不解释为什么认为你是错的人...这样就只能假设它们不同意你的观点(而不是事实问题)。 - RHSeeger
@RHSeeger - 确实。无法知道它是一个合法的投诉还是只是一个 SCITE。 - danben

1

这些函数自然而然地应该是非静态的。但如果您有疑问,请参考GRASP,他们描述了这样的事情。

根据GRASP信息专家的说法,这些函数不应该是静态的。

尽管没有关于静态方法的直接信息,但是

信息专家将引导我们将责任放在需要最多信息来履行其职责的类中。

如果您使您的方法静态,那么您将把逻辑进一步移离实际数据,并且必须将数据传递给方法。

去除静态将使逻辑更接近使用它的数据。


根据我看到的,你提供的GRASP页面似乎并没有明确指示方法是否应该是静态的,只是表明它们应该是相关类的方法(在这两种情况下都是如此)。 - RHSeeger
为什么它们必须是实例方法?你能引用任何特定的原因吗? - danben
静态方法无法访问实例数据。根据信息专家的建议,您应该将方法放置在可以访问数据的位置。移除静态方法可以使逻辑更接近数据。 - Mykola Golubyev

1
如果你要使用Java并创建对象,那么从风格上讲,我认为你应该尽可能地利用对象和数据封装。对我来说,这意味着将数据留在原处(在Point类中),而不是将其传递给单独的方法来处理。让你的对象为你工作;不仅仅是拥有getter和setter。实际上,认真考虑如何避免需要getter。
在不可变类上具有像add()和subtract()这样的方法,并返回不可变类的新实例是非常常见的。这是函数式编程的良好风格,对于这样的类来说完全合理。(参见BigInteger或BigDecimal的良好示例。请勿参见Date或Calendar的糟糕、错误和可怕的示例。:)
将方法保留在类中可以让您选择性地定义这些类可能实现的接口,使用Decorator或Adapter模式,编写某些类型的测试等。

0

我在这方面往往与常规相反,但两种方式对我来说都是合理的。

  • 这些方法显然应该是Point方法的一部分,因为它们专门处理点
  • 对于使用两个点的方法,它们没有任何暗示表明它们需要更多关于其中一个点的信息... 因此,没有推动力使方法成为非静态成员的哪个实例。

对于像Java这样的语言,我会选择静态方法,特别是因为上述第二点。对于具有运算符重载功能(如Ruby)的语言,我会选择实例方法以利用它。


给投票负方的用户:通常最好留一个评论来解释你的反对票。 - RHSeeger

0
将它们设为静态也会使单元测试变得更困难!在.NET中,我所知道的唯一能处理这个问题的模拟框架是TypeMock。
如果意图是使这个类不可变,那么在任何访问器调用中都会返回新的Point对象,所以将它们设为静态在这里并没有太多意义。

-1

这些方法应该是静态的,因为类本身适合通过构造函数创建并分配值,由于x和y是final,只需创建一次。这意味着您可以创建点,但不能在将来操作其数据。Add/Substract/Etc方法是实用程序方法,不需要使用Point的实例。


2
定义类似于变量的方法但在不可变对象上返回新实例是完全合理的。这是一种常见的函数式编程实践,也是编写线程安全Java的好方法。 - Alex Miller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接