Интерфейсы со статическими полями в Java для совместного использования «констант» - PullRequest
110 голосов
/ 26 ноября 2008

Я смотрю на некоторые проекты 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

}

Ответы [ 8 ]

154 голосов
/ 26 ноября 2008

Обычно считается плохой практикой. Проблема заключается в том, что константы являются частью общедоступного «интерфейса» (если не сказать лучшего слова) класса реализации. Это означает, что реализующий класс публикует все эти значения во внешних классах, даже если они требуются только для внутреннего использования. Константы распространяются по всему коду. Примером является интерфейс SwingConstants в Swing, который реализуется десятками классов, которые все «реэкспортируют» все его констант (даже тех, которые они не используют) как свои.

Но не просто поверьте мне на слово, Джош Блох также говорит это плохо:

Константный шаблон интерфейса плохо использует интерфейсы. То, что класс использует некоторые константы внутри, является деталью реализации. Реализация постоянного интерфейса вызывает утечку этой детали реализации в экспортируемый API класса. Для пользователей класса не имеет значения, что класс реализует постоянный интерфейс. На самом деле, это может даже запутать их. Хуже того, он представляет собой обязательство: если в будущем выпуске класс будет изменен так, что ему больше не нужно будет использовать константы, он все равно должен реализовать интерфейс для обеспечения двоичной совместимости. Если нефинальный класс реализует постоянный интерфейс, все его подклассы будут иметь свои пространства имен, загрязненные константами в интерфейсе.

Перечисление может быть лучшим подходом. Или вы можете просто поместить константы как открытые статические поля в классе, который не может быть создан. Это позволяет другому классу получать к ним доступ, не загрязняя собственный API.

95 голосов
/ 26 ноября 2008

Вместо реализации «интерфейса констант» в 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;
}
8 голосов
/ 15 октября 2012

Я не претендую на право быть правым, но давайте посмотрим на этот небольшой пример:

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 

И теперь, если нам когда-нибудь понадобится изменить значение ядра, если ДВИГАТЕЛЬ или КОЛЕСО, мы можем изменить интерфейс ToyotaCar на уровне абстракции, не затрагивая реализации

Это НЕ БЕЗОПАСНО, я знаю, но я все еще хочу знать, что вы думаете об этом

6 голосов
/ 19 августа 2015

В 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» является непонятным именем, вы можете изменить его на «insideDistanceInKm» и знать, что новый синтаксис будет соблюдаться всеми вашими реализациями индекса.

PS: Вдохновение для этого примера взято из пространственного кода Neo4j.

5 голосов
/ 01 марта 2012

Учитывая преимущество задним числом, мы видим, что Java нарушается во многих отношениях. Одним из основных недостатков Java является ограничение интерфейсов абстрактными методами и конечными статическими полями. Более новые, более сложные ОО-языки, такие как Scala, включают интерфейсы по признакам, которые могут (и обычно имеют) включать конкретные методы, которые могут иметь нулевую степень (константы!). Для описания характеристик как единиц составного поведения см. http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf. Краткое описание того, как характеристики в Scala сравниваются с интерфейсами в Java, см. http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. В контексте обучения ОО-проектированию, упрощенные правила как утверждение, что интерфейсы никогда не должны включать статические поля, глупо. Многие признаки естественно включают в себя константы, и эти константы являются подходящей частью общедоступного «интерфейса», поддерживаемого этой чертой. При написании Java-кода не существует чистого, элегантного способа представления признаков, но использование статических полей final в интерфейсах часто является частью хорошего обходного пути.

0 голосов
/ 08 января 2013

У меня недостаточно репутации, чтобы комментировать Плерок, поэтому я должен создать ответ. Я прошу прощения за это, но он приложил немало усилий, и я хотел бы ответить ему.

Плерок, вы создали прекрасный пример, чтобы показать, почему эти константы должны быть независимыми от интерфейсов и независимыми от наследования. Для клиента приложения не важно, что существует техническая разница между этими реализациями автомобилей. Они одинаковы для клиента, просто автомобили. Итак, клиент хочет взглянуть на них с той точки зрения, это интерфейс, подобный I_Somecar. На протяжении всего приложения клиент будет использовать только одну перспективу, а не разные для каждой марки автомобиля.

Если клиент хочет сравнить автомобили перед покупкой, он может использовать такой метод:

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

Интерфейс - это контракт о поведении, который показывает разные объекты с одной точки зрения. Как бы вы его ни проектировали, у каждой автомобильной марки будет своя линия наследования. Хотя это на самом деле совершенно правильно, потому что автомобили могут быть настолько разными, что это может быть похоже на сравнение совершенно разных типов объектов, в конце концов, есть выбор между разными автомобилями. И это перспектива интерфейса, которым должны обладать все бренды. Выбор констант не должен сделать это невозможным. Пожалуйста, примите во внимание ответ Зарконнена.

0 голосов
/ 14 ноября 2012

Согласно спецификации JVM, поля и методы в интерфейсе могут иметь только Public, Static, Final и Abstract. Ссылка изнутри Java VM

По умолчанию все методы в интерфейсе абстрактны, даже если вы не упомянули об этом явно.

Интерфейсы предназначены только для спецификации. Он не может содержать никаких реализаций. Поэтому, чтобы избежать реализации классов для изменения спецификации, она делается окончательной. Поскольку интерфейс не может быть создан, они становятся статическими для доступа к полю с использованием имени интерфейса.

0 голосов
/ 26 ноября 2008

Это произошло еще до того, как появилась Java 1.5 и принесла нам перечисления. До этого не было хорошего способа определить набор констант или ограниченных значений.

Это все еще используется, в большинстве случаев, либо для обратной совместимости, либо из-за объема рефакторинга, необходимого для избавления, во многих проектах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...