与java中的静态字段接口以共享“常量”

问题

我正在看一些开源Java项目进入Java并注意到它们中有很多都有某种"常量"接口。

例如,processing.org有一个名为PConstants.java的接口,其他大多数核心类都实现了这个接口。界面充满了静态成员。有这种方法的理由,还是这被认为是不好的做法?为什么不在有意义的地方使用enums,或者使用静态类?

我觉得使用接口允许某种伪'全局变量'很奇怪。

public interface PConstants {

  // LOTS OF static fields...

  static public final int SHINE = 31;

  // emissive (by default kept black)
  static public final int ER = 32;
  static public final int EG = 33;
  static public final int EB = 34;

  // has this vertex been lit yet
  static public final int BEEN_LIT = 35;

  static public final int VERTEX_FIELD_COUNT = 36;


  // renderers known to processing.core

  static final String P2D    = "processing.core.PGraphics2D";
  static final String P3D    = "processing.core.PGraphics3D";
  static final String JAVA2D = "processing.core.PGraphicsJava2D";
  static final String OPENGL = "processing.opengl.PGraphicsOpenGL";
  static final String PDF    = "processing.pdf.PGraphicsPDF";
  static final String DXF    = "processing.dxf.RawDXF";


  // platform IDs for PApplet.platform

  static final int OTHER   = 0;
  static final int WINDOWS = 1;
  static final int MACOSX  = 2;
  static final int LINUX   = 3;

  static final String[] platformNames = {
    "other", "windows", "macosx", "linux"
  };

  // and on and on

}

#1 热门回答(148 赞)

这通常被认为是不好的做法。问题是常量是实现类的公共"接口"(缺少更好的词)的一部分。这意味着实现类将所有这些值发布到外部类,即使它们仅在内部需要。常量在整个代码中激增。一个例子是Swing中的SwingConstants接口,它由几十个类实现,这些类都将其所有常量(甚至是它们不使用的常量)"重新导出"为自己的。

但是,不要只听我的话,Josh Bloch also says的不好:

常量接口模式是接口的不良使用。一个类在内部使用一些常量是一个实现细节。实现常量接口会导致此实现细节泄漏到类的导出API中。类的用户实现一个常量接口并不重要。事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一种承诺:如果在将来的版本中修改了类以便它不再需要使用常量,它仍然必须实现接口以确保二进制兼容性。如果非最终类实现了一个常量接口,那么它的所有子类的命名空间都会受到接口中常量的污染。

枚举可能是更好的方法。或者你可以简单地将常量作为公共静态字段放在无法实例化的类中。这允许另一个类访问它们而不会污染自己的API。


#2 热门回答(92 赞)

在Java 1.5中,你可以使用静态导入从另一个类/接口导入常量/静态方法,而不是实现"常量接口":

import static com.kittens.kittenpolisher.KittenConstants.*;

这避免了使你的类实现没有功能的接口的丑陋。

至于有一个类来存储常量的做法,我认为有时是必要的。某些常量在一个类中没有自然的位置,因此最好将它们放在"中立"的位置。

但是不使用接口,而是使用带有私有构造函数的final类。 (使得无法对类进行实例化或子类化,发送一条强大的消息,表明它不包含非静态功能/数据。)

例如:

/**Set of constants needed for Kitten Polisher. */
public final class KittenConstants
{
    private KittenConstants() {}

    public static final String KITTEN_SOUND = "meow";
    public static final double KITTEN_CUTENESS_FACTOR = 1;
}

#3 热门回答(7 赞)

我不假装正确的权利,但让我们看看这个小例子:

public interface CarConstants {

      static final String ENGINE = "mechanical";
      static final String WHEEL  = "round";
      // ...

}

public interface ToyotaCar extends CarConstants //, ICar, ... {
      void produce();
}

public interface FordCar extends CarConstants //, ICar, ... {
      void produce();
}

// and this is implementation #1
public class CamryCar implements ToyotaCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

// and this is implementation #2
public class MustangCar implements FordCar {

      public void produce() {
           System.out.println("the engine is " + ENGINE );
           System.out.println("the wheel is " + WHEEL);
      }
}

ToyotaCar对FordCar一无所知,而FordCar也不了解ToyotaCar。原则CarConstants应该改变,但......

常量不应该改变,因为车轮是圆形的,而且是机械的,但是......未来丰田的研究工程师发明了电子发动机和平轮!让我们看看我们的新界面

public interface InnovativeCarConstants {

          static final String ENGINE = "electronic";
          static final String WHEEL  = "flat";
          // ...
}

现在我们可以改变我们的抽象:

public interface ToyotaCar extends CarConstants

public interface ToyotaCar extends InnovativeCarConstants

现在如果我们需要更改核心价值,如果我们可以在ENGINE或WHEEL上更改抽象级别的ToyotaCar接口,请不要触及实现

我知道,这不是安全的,但我仍然想知道你是否考虑过这一点