Java:何时使方法静态v。实例

时间:2009-10-24 20:20:05

标签: java oop static

我有一个跟踪基因的Gene课程。 Gene有一种计算两个基因之间距离的方法。有没有理由让它变得静止?

哪个更好?

public static int geneDistance(Gene g0, Gene g1)

public int geneDistance(Gene other)

支持/反对使其静止的论据?我理解成员静态意味着什么,我只关心它对最大清洁度/效率等的影响。

我重复相同的模式,返回两个基因的修剪版本,找到基因之间的匹配,找到动物之间的匹配(包含基因集合)等。

16 个答案:

答案 0 :(得分:21)

实例,而非静态


对于这种情况,我认为第二种选择显然更好。如果你考虑一下,如果你愿意将对象传递给它,任何方法都可以实现为静态,只有似乎是一个特例,因为另一个参数也是一个实例。

因此,我们通过在点运算符的两个实例对象之间进行选择来略微冒犯我们对对称和抽象的搜索。但是,如果您将.method视为.然后运算符,那么这不是一个真正的问题。

另外,进行功能样式链接的唯一方法是使用属性,即实例方法。您可能希望thing.up.down.parent.next.distance(x)起作用。

答案 1 :(得分:14)

当你创建一个静态方法时,这意味着可以在没有类实例的情况下调用该方法。它还意味着该方法无法访问实例变量,除非它传递对象的引用。

有时,将方法设为静态是有意义的,因为该方法与类相关联,但不与类的特定实例相关联。例如,所有parseX方法,例如Integer.parseInt(String s)。这会将String转换为int,但与Integer对象的特定实例无关。

另一方面,如果方法必须返回一些对象的特定实例唯一的数据(如大多数getter和setter方法),那么它就不能是静态的。

答案 2 :(得分:8)

IMO没有绝对的“更好”,但public int geneDistance(Gene other)在风格上与Java中的其他方法更相似(例如Object.equals,Comparable.compareTo),所以我会这样做。

答案 3 :(得分:8)

我更喜欢第二种形式,即实例方法,原因如下:

  1. 静态方法使测试变得困难,因为它们无法替换,
  2. 静态方法更加面向程序(因此不太面向对象)。
  3. IMO,静态方法适用于实用程序类(如StringUtils),但我不想滥用它们。

答案 4 :(得分:7)

我对Charle回答的重写:

如果有问题的方法打算以任何方式使用底层对象的状态,请将其设为实例方法。否则,让它静止。

这取决于对象类的设计方式。

在你的情况下,alphazero,可能 int geneDistance(基因g0,基因g1) 实际上取决于基因的状态实例它被调用。我会使这个方法静态。并将它放在像 GeneUtils 这样的实用程序类中。

当然,我可能还有其他方面的问题我不知道,但这是我使用的规则的一般大拇指。

P.S。 - >我不会将该方法放在 Gene 类本身的原因是因为Gene不应该负责计算它与另一个Gene的距离。 ; - )

答案 5 :(得分:3)

public static int geneDistance(Gene g0, Gene g1)将成为Java中CollectionsArrays的单独实用程序类的一部分,而public int geneDistance(Gene other)将成为Gene类的一部分。考虑到你有其他操作,如“两个基因的修剪版本,找到基因之间的匹配,找到动物之间的匹配(包含基因集合)等”我将为它们创建一个单独的静态实用程序类,因为这些操作在语义上没有意义到Gene是什么。

如果“基因距离”的语义可以包含在您的equals(Object o)方法中,那么您可以在那里使用它,或者将其包含在您的静态实用程序中。

答案 6 :(得分:2)

我想开始用新问题回答你的问题:你的班级基因负责什么?可能你听说过“单一责任原则”:一个班级应该只有一个改变的理由。所以,我相信如果您回答这个问题,您将能够决定如何设计您的应用程序。在这种特殊情况下,我既不会使用第一种方法也不会使用第二种方法。在我看来,定义新的责任并将其封装在一个单独的类中可能更好,或者可能是一个函数。

答案 7 :(得分:2)

我将尝试总结一下我已经同意的一些要点。

我个人认为没有“感觉更好”的答案。有效的原因确实存在为什么你不是一个充满静态方法的实用程序类。

简短的回答是,在面向对象的世界中,你应该使用对象和它们附带的所有好东西(封装,多态)

多态性

如果计算基因之间的距离的方法变化,则大致(更可能是Strategy)每个变异都有一个基因类。封装各种不同的东西。否则,你最终会得到多个ifs。

Open For Extension, Closed for Modification

这意味着,如果计算基因之间距离的新方法出现在线下,则不应修改现有代码,而应添加新代码。否则你就有可能破坏已有的东西。

在这种情况下,您应该添加一个新的Gene类,而不是修改 #geneDistance

中编写的代码

Tell Don't Ask

你应该告诉你的对象该做什么,不要问他们的状态并为他们做出决定。突然间你打破了single responsibility princi,因为那是多态性。

可测

静态方法可能很容易单独测试,但在未来的路上,您将在其他类中使用此静态方法。在隔离测试这些类时,您将很难做到这一点。或者更确切地说不是。

我会让Misko有他的说法,这比我能提出的更好。

import junit.framework.Assert;

import org.junit.Test;

public class GeneTest
{
    public static abstract class Gene
    {
        public abstract int geneDistance(Gene other);
    }

    public static class GeneUtils
    {
        public static int geneDistance(Gene g0, Gene g1)
        {
            if( g0.equals(polymorphicGene) )
                return g0.geneDistance(g1);
            else if( g0.equals(oneDistanceGene) )
                return 1;
            else if( g0.equals(dummyGene) )
                return -1;
            else
                return 0;            
        }
    }


    private static Gene polymorphicGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return other.geneDistance(other);
                                        }
                                    };

    private static Gene zeroDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 0;
                                        }
                                    };

    private static Gene oneDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 1;
                                        }
                                    };

    private static Gene hardToTestOnIsolationGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return GeneUtils.geneDistance(this, other);
                                        }
                                    };

    private static Gene dummyGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return -1;
                                        }
                                    };                                    
    @Test
    public void testPolymorphism()
    {
        Assert.assertEquals(0, polymorphicGene.geneDistance(zeroDistanceGene));
        Assert.assertEquals(1, polymorphicGene.geneDistance(oneDistanceGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }

    @Test
    public void testTestability()
    {

        Assert.assertEquals(0, hardToTestOnIsolationGene.geneDistance(dummyGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }    

    @Test
    public void testOpenForExtensionClosedForModification()
    {

        Assert.assertEquals(0, GeneUtils.geneDistance(polymorphicGene, zeroDistanceGene));
        Assert.assertEquals(1, GeneUtils.geneDistance(oneDistanceGene, null));
        Assert.assertEquals(-1, GeneUtils.geneDistance(dummyGene, null));
    }    
}

答案 8 :(得分:1)

这是一个元答案,也是一个有趣的练习:调查一堆Java SDK的库类,看看是否可以对不同类中的静态方法之间的共性进行分类。

答案 9 :(得分:1)

在这种特殊情况下,我将使其成为一种intance方法。但是如果你在g0为null时有一个合乎逻辑的答案,那么就使用BOTH(这种情况比你想象的更频繁)。

例如,aString.startsWith(),如果aString为null,您可能认为返回null是LOGICAL(如果您认为该函数可以为NULL-TOLERATE)。这允许我稍微简化我的程序,因为不需要在客户端代码中使用aString检查null。


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if (MyString.startsWith(aString, aPrefix))
        aStrings.aStringadd();
}

而不是


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if ((aString != null) && aString.startsWith(aPrefix))
        aStrings.aStringadd();
}

注意:这是一个过于简化的例子。

只是一个想法。

答案 10 :(得分:1)

我会把它变成一个实例方法。但这可能是因为我对基因没有任何线索;)

实例方法可以被子类覆盖,这大大降低了代码的复杂性(减少了对if语句的需求)。在静态方法示例中,如果我得到一种特定类型的基因,其距离的计算方式不同,会发生什么?广告另一种静态方法?如果你必须处理基因的多态性列表,你必须寻找一种基因类型来选择正确的距离方法......这会增加耦合和复杂性。

答案 11 :(得分:0)

我会选择第二种方法。我认为使方法静态没有任何好处。由于该方法位于Gene类中,因此将其设置为静态只会添加一个额外的参数而不会产生额外的增益。如果你需要一个util类,那就完全不同了。但是在我看来,如果可以将方法添加到相关类中,通常不需要util类。

答案 12 :(得分:0)

我认为问题域应该超出一般的风格和/或OO考虑因素。

例如,我猜测对于遗传分析领域,“基因”和“距离”的概念是相当具体的,不需要通过继承进行专业化。如果不是这种情况,可以选择实例方法。

答案 13 :(得分:0)

更喜欢实例方法的主要原因是多态性。子类不能覆盖静态方法,这意味着您无法基于实例类型自定义实现。这可能不适用于您的情况,但值得一提。

如果基因距离完全独立于基因的类型,我宁愿使用单独的实用程序类来使该独立性更明确。将geneDistance方法作为Gene类的一部分意味着距离是与基因实例相关的行为。

答案 14 :(得分:0)

我的回答非常自以为是。

我将采用与StringUtils中StringUtils.getLevenshteinDistance实现之一相同的方式。

    public interface GeneDistance{
        public int get();
    }

    public class GeneDistanceImpl implements GeneDistance{
        public int get(){ ... }
    }

    public class GeneUtils{
        public static int geneDistance(Gene g0, Gene g1){
            return new GeneDistanceImpl(g0, g1).get();
        }
    }

这样做的一些要点

  • 可能有多个距离实现,因此实用方法比g0.distanceTo(g1)
  • 更优选
  • 我可以静态导入它以获得简短的符号
  • 我可以测试我的实施
  • 我还可以添加:

    class Gene{
        // ... Gene implementation ...
    
        public int distanceTo(Gene other){
            return distance.get(this, GeneUtils.getDefaultDistanceImpl());
        }
    
        public int distanceTo(Gene other, GeneDistance distance){
            return distance.get(this, other);
        }
    }
    

使复杂方法完全静止的一个原因是性能。 static关键字是JIT编译器的提示,可以内联该方法。在我看来,除非他们的方法调用几乎是瞬时的 - 不到一微秒,即一些字符串操作或简单的计算,否则你不需要打扰这些事情。这可能是Levenshtein距离在最新实施中完全静止的原因。

答案 15 :(得分:0)

未提及的两个重要考虑因素是gene1geneDistance(gene2)是否总是与gene2.geneDistance(gene1)匹配,以及Gene是否始终是密封类。实例方法在调用它们的事物类型方面是多态的,而不是它们的参数类型。如果距离函数应该是可传递的,那么这可能会引起一些混淆,但是不同类型的东西可能会以不同的方式计算距离。如果距离函数应该是可传递的,并且被定义为任何一个类知道的最短变换,那么一个好的模式可能是拥有受保护的实例方法int getOneWayDistance(Gene other)然后具有类似的东西:

public static int geneDistance(Gene g0, Gene g1)
{
  int d0=g0.getOneWayDistance(g1);
  int d1=g1.getOneWayDistance(g0);
  if (d0 < d1) return d0; else return d1;
}

这样的设计将确保距离关系具有传递性,同时允许各个类型向其他类型的实例报告其他类型可能不了解的实例的快捷方式。