【问题标题】:Java: When to make methods static v. instance [closed]Java:何时将方法设为静态 v. 实例 [关闭]
【发布时间】:2010-12-09 18:59:10
【问题描述】:

我有一个跟踪基因的Gene 类。 Gene 有一个计算两个基因之间距离的方法。有什么理由让它成为静态的吗?

哪个更好?

public static int geneDistance(Gene g0, Gene g1)

public int geneDistance(Gene other)

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

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

【问题讨论】:

  • 多么有趣的问题。我有自己的看法,但我觉得它们除了“味道”之外没有什么资格,所以我不会回答。
  • 这应该是社区维基。
  • 请考虑使用名称“distanceTo(Gene that)”。您有一个隐含的上下文,应该反映在命名中。
  • ...或者更好的“getDistanceTo(Gene g)”。

标签: java oop static


【解决方案1】:

实例,非静态


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

因此,我们对对称性和抽象性的探索会因必须在点运算符的两个实例对象之间进行选择而受到轻微冒犯。但是,如果您将.method 视为. 然后operator,这并不是真正的问题。

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

【讨论】:

    【解决方案2】:

    当您将方法设为静态时,这意味着可以在没有类实例的情况下调用该方法。这也意味着该方法不能访问实例变量,除非它被传递给一个对象的引用。

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

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

    【讨论】:

    • “这也意味着方法本身只能访问也是静态的类变量。”嗯?给定实例a 的静态方法可以访问a 的所有字段和方法。
    • 他的意思是他们无法访问this,因为不需要调用它的实例。
    • 是的——我应该说得更像:一个静态方法不能访问实例变量,除非它被传递一个对对象的引用
    • 我认为@Charles 的发言没有参考 OP 的 public static int geneDistance(Gene g0, Gene g1)。他显然指的是作用于类而不是传入实例的类内静态方法。
    • 查尔斯,你可以编辑你的答案来澄清。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      1. 静态方法使测试变得困难,因为它们无法被替换,
      2. 静态方法更面向过程(因此更少面向对象)。

      IMO,静态方法适用于实用程序类(如 StringUtils),但我不想滥用它们。

      【讨论】:

      • +1 请注意,静态导入可以使实用程序类非常干净,这对于静态来说很少见。例如类似于按合同设计的断言框架。
      • 这是真的,例如JUnit 4,我喜欢它。虽然没有看到 +1 :)
      【解决方案5】:

      我对查理的回答的改写:

      如果所讨论的方法打算以任何方式使用底层 object 的状态,请将其设为实例方法。否则,将其设为静态。

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

      在你的情况下,alphazero,可能 int geneDistance(Gene g0, Gene g1) 真的取决于 Gene的状态> 调用它的实例。我会让这个方法成为静态的。并把它放在像 GeneUtils 这样的实用程序类中。

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

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

      【讨论】:

      • @adwiv 好吧,getter 方法永远不可能是静态的,因为总是必须有一个实例来“获取”属性。但是,是的,我明白你的意思。相应地编辑了我的答案。
      • +1 并删除我的评论,因为它在编辑后看起来令人困惑:)
      【解决方案6】:

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

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

      【讨论】:

      • 实际上,我最初有一个静态实用程序,但后来将所有这些功能移到了GeneSpecies。为什么这些在语义上与基因相关?
      • 我认为 Collections 实用程序类已经存在,因此第三方可以更轻松地实现各种 Collection 接口,而无需实现自己的 min、rotate、sort 等方法。对于 Gene 类,这不是问题。考虑“java.awt.Point”类,没有用于计算点之间距离的 Points 实用程序类。
      • @Sam 我不仅在查看单个距离检查,还查看修剪、匹配、收集匹配以及他决定放入的任何其他内容,这与 Collections 代表的内容保持一致. @Rosarch 当我说与语义无关时,我的意思是找到像多个基因的修剪版本这样的问题并不一定对基因有意义(我当然可能是错的,因为我不是生物学家哈哈 :))但希望你能抓住我的漂移。
      【解决方案7】:

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

      【讨论】:

      • +1 终于有人明白OO的真谛了!
      【解决方案8】:

      我将尝试总结这里已经给出的一些我同意的观点。

      我个人认为没有“感觉更好”的答案。 确实存在为什么您不希望实用程序类填充静态方法的正当理由。

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

      多态性

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

      Open For Extension, Closed for Modification

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

      在这种情况下你应该添加一个新的基因类,而不是修改写在#geneDistance

      中的代码

      Tell Don't Ask

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

      可测试性

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

      我会让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));
          }    
      }
      

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 不幸的是,没有元投票。 ;)
        【解决方案10】:

        在这种特殊情况下,我会将其设为实例方法。但是,如果您在 g0 为空时有一个合乎逻辑的答案,那么请同时使用 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();
        }
        

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

        只是一个想法。

        【讨论】:

          【解决方案11】:

          我会将其设为实例方法。但这可能是因为我对基因一无所知;)

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

          【讨论】:

            【解决方案12】:

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

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:

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

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

                【讨论】:

                  【解决方案15】:

                  我的回答很有主见。

                  我会采用与 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 距离在最新实现中完全静态的原因。

                  【讨论】:

                    【解决方案16】:

                    没有提到的两个重要考虑因素是gene1.geneDistance(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;
                    }
                    

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-08-16
                      • 2015-08-12
                      • 1970-01-01
                      • 2014-09-13
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多