[java] ‘상수’공유를위한 자바의 정적 필드와 인터페이스

Java에 들어가기 위해 일부 오픈 소스 Java 프로젝트를 살펴보고 있으며 많은 프로젝트에 일종의 ‘상수’인터페이스가 있음을 알 수 있습니다.

예를 들어 processing.org 에는 PConstants.java 라는 인터페이스가 있으며 대부분의 다른 핵심 클래스는이 인터페이스를 구현합니다. 인터페이스는 정적 멤버로 가득 차 있습니다. 이 접근 방식에 대한 이유가 있습니까, 아니면 나쁜 습관으로 간주됩니까? 말이되는 곳에 열거 형 이나 정적 클래스를 사용하지 않는 이유는 무엇 입니까?

일종의 의사 ‘전역 변수’를 허용하기 위해 인터페이스를 사용하는 것이 이상합니다.

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

}



답변

일반적으로 나쁜 습관으로 간주됩니다. 문제는 상수가 구현 클래스의 공용 “인터페이스”(더 나은 단어를 원하는 경우)의 일부라는 것입니다. 이는 구현 클래스가 내부적으로 만 필요한 경우에도 이러한 모든 값을 외부 클래스에 게시 함을 의미합니다. 상수는 코드 전체에서 확산됩니다. 예를 들어 Swing 의 SwingConstants 인터페이스는 모든 상수 (사용하지 않는 상수도 포함)를 자체적으로 “다시 내보내는”수십 개의 클래스에 의해 구현됩니다 .

그러나 내 말을 받아들이지 마십시오. Josh Bloch는 또한 그것이 나쁘다고 말합니다 .

상수 인터페이스 패턴은 인터페이스를 제대로 사용하지 않는 것입니다. 클래스가 내부적으로 일부 상수를 사용한다는 것은 구현 세부 사항입니다. 상수 인터페이스를 구현하면이 구현 세부 사항이 클래스의 내 보낸 API로 유출됩니다. 클래스가 상수 인터페이스를 구현하는 것은 클래스 사용자에게 중요하지 않습니다. 사실, 혼란 스러울 수도 있습니다. 더 나쁜 것은 약속을 나타냅니다. 향후 릴리스에서 더 이상 상수를 사용할 필요가 없도록 클래스가 수정되는 경우에도 이진 호환성을 보장하기 위해 인터페이스를 구현해야합니다. 최종 클래스가 아닌 클래스가 상수 인터페이스를 구현하는 경우 모든 하위 클래스는 인터페이스의 상수에 의해 네임 스페이스가 오염됩니다.

열거 형이 더 나은 방법 일 수 있습니다. 또는 인스턴스화 할 수없는 클래스의 공용 정적 필드로 상수를 간단히 넣을 수 있습니다. 이를 통해 다른 클래스가 자체 API를 오염시키지 않고 액세스 할 수 있습니다.


답변

Java 1.5 이상에서는 “상수 인터페이스”를 구현하는 대신 정적 가져 오기를 사용하여 다른 클래스 / 인터페이스에서 상수 / 정적 메서드를 가져올 수 있습니다.

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

이렇게하면 클래스에서 기능이없는 인터페이스를 구현하는 추악함을 방지 할 수 있습니다.

상수를 저장하기 위해 클래스를 갖는 연습은 때때로 필요하다고 생각합니다. 클래스에서 자연스러운 위치가없는 특정 상수가 있으므로 “중립”위치에 두는 것이 좋습니다.

그러나 인터페이스를 사용하는 대신 개인 생성자가있는 최종 클래스를 사용하십시오. (클래스를 인스턴스화하거나 하위 클래스 화하는 것을 불가능하게하여 비 정적 기능 / 데이터를 포함하지 않는다는 강력한 메시지를 보냅니다.)

예 :

/** 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;
}


답변

나는 옳은 척하지 않지만이 작은 예를 봅시다.

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는 변경되어야하지만 …

바퀴가 둥글고 egine이 기계식이기 때문에 상수를 변경해서는 안되지만 … 미래에 Toyota의 연구 엔지니어는 전자 엔진과 평면 바퀴를 발명했습니다! 우리의 새로운 인터페이스를 보자

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 인터페이스를 변경할 수 있습니다. 구현은 건드리지 마십시오.

안전하지 않습니다. 알아요.하지만 당신이 이것에 대해 생각하는지 알고 싶어요


답변

Java에서이 패턴에 대한 많은 증오가 있습니다. 그러나 정적 상수의 인터페이스에는 때때로 가치가 있습니다. 기본적으로 다음 조건을 충족해야합니다.

  1. 개념은 여러 클래스의 공용 인터페이스의 일부입니다.

  2. 해당 값은 향후 릴리스에서 변경 될 수 있습니다.

  3. 모든 구현이 동일한 값을 사용하는 것이 중요합니다.

예를 들어 가상 쿼리 언어에 대한 확장을 작성한다고 가정합니다. 이 확장에서는 인덱스에서 지원하는 몇 가지 새로운 작업으로 언어 구문을 확장 할 것입니다. 예를 들어 지리 공간 쿼리를 지원하는 R-Tree가 있습니다.

따라서 정적 상수로 공용 인터페이스를 작성합니다.

public interface SyntaxExtensions {
     // query type
     String NEAR_TO_QUERY = "nearTo";

     // params for query
     String POINT = "coordinate";
     String DISTANCE_KM = "distanceInKm";
}

이제 나중에 새로운 개발자가 더 나은 인덱스를 구축해야한다고 생각하고 와서 R * 구현을 구축합니다. 그의 새 트리에서이 인터페이스를 구현함으로써 그는 서로 다른 인덱스가 쿼리 언어에서 동일한 구문을 갖도록 보장합니다. 또한 나중에 “nearTo”가 혼란스러운 이름이라고 결정한 경우 “withinDistanceInKm”으로 변경할 수 있으며 모든 인덱스 구현에서 새 구문이 존중된다는 것을 알 수 있습니다.

추신 :이 예제의 영감은 Neo4j 공간 코드에서 가져 왔습니다.


답변

돌이켜 보면 Java가 여러면에서 망가 졌음을 알 수 있습니다. Java의 주요 실패 중 하나는 추상 메서드 및 정적 최종 필드에 대한 인터페이스 제한입니다. Scala와 같은 새롭고 더 정교한 OO 언어는 특성에 따라 인터페이스를 포함 할 수 있으며 일반적으로 인수가 0 (상수!) 일 수있는 구체적인 메서드를 포함 할 수 있습니다. 구성 가능한 동작의 단위로서 특성에 대한 설명은 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf를 참조 하십시오 . Scala의 특성이 Java의 인터페이스와 어떻게 비교되는지에 대한 간략한 설명은 http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5를 참조하십시오 .. OO 디자인을 가르치는 맥락에서 인터페이스가 정적 필드를 포함해서는 안된다고 주장하는 것과 같은 단순한 규칙은 어리 석습니다. 많은 특성은 자연스럽게 상수를 포함하며 이러한 상수는 특성이 지원하는 공용 “인터페이스”의 일부입니다. Java 코드를 작성할 때 특성을 표현하는 깔끔하고 우아한 방법은 없지만 인터페이스 내에서 정적 최종 필드를 사용하는 것은 종종 좋은 해결 방법의 일부입니다.


답변

JVM 사양에 따라 인터페이스의 필드와 메서드는 Public, Static, Final 및 Abstract 만 가질 수 있습니다. 내부 Java VM의 참조

기본적으로 인터페이스의 모든 메소드는 명시 적으로 언급하지 않았더라도 추상적입니다.

인터페이스는 사양 만 제공하기위한 것입니다. 어떤 구현도 포함 할 수 없습니다. 따라서 사양을 변경하는 클래스 구현을 피하기 위해 최종적으로 작성됩니다. Interface는 인스턴스화 할 수 없기 때문에 인터페이스 이름을 사용하여 필드에 액세스하기 위해 정적으로 만들어집니다.


답변

나는 Pleerock에 코멘트를 할만큼 평판이 충분하지 않기 때문에 답을 만들어야합니다. 미안하지만 그는 그것에 대해 좋은 노력을 기울 였고 나는 그에게 대답하고 싶습니다.

Pleerock, 당신은 왜 이러한 상수가 인터페이스로부터 독립적이고 상속으로부터 독립적이어야하는지 보여주는 완벽한 예제를 만들었습니다. 응용 프로그램의 클라이언트에게는 이러한 자동차 구현간에 기술적 차이가 있다는 것이 중요하지 않습니다. 클라이언트도 마찬가지입니다. 자동차 만 있습니다. 그래서 클라이언트는 I_Somecar와 같은 인터페이스 인 그 관점에서 그것들을보고 싶어합니다. 응용 프로그램 전체에서 클라이언트는 각기 다른 자동차 브랜드에 대해 다른 관점이 아닌 하나의 관점 만 사용합니다.

고객이 구매하기 전에 자동차를 비교하려는 경우 다음과 같은 방법을 사용할 수 있습니다.

public List<Decision> compareCars(List<I_Somecar> pCars);

인터페이스는 행동에 대한 계약이며 한 관점에서 다른 객체를 보여줍니다. 당신이 디자인하는 방식에 따라 모든 자동차 브랜드는 고유 한 상속을 받게됩니다. 실제로는 매우 정확하지만, 자동차는 완전히 다른 유형의 물체를 비교하는 것과 같을 수 있다는 점에서 차이가있을 수 있기 때문에 결국에는 서로 다른 자동차 사이의 선택이 있습니다. 이것이 모든 브랜드가 공유해야하는 인터페이스의 관점입니다. 상수의 선택이 이것을 불가능하게해서는 안됩니다. Zarkonnen의 대답을 고려하십시오.