Интерфейсы со статическими полями в 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

}

Ответ 1

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

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

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

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

Ответ 2

Вместо реализации "интерфейса констант" в 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;
}

Ответ 3

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

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

to

public interface ToyotaCar extends InnovativeCarConstants 

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

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

Ответ 4

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

Ответ 5

В Java существует много ненависти к этому шаблону. Однако интерфейс статических констант иногда имеет значение. Вам необходимо в основном выполнить следующие условия:

  • Концепции являются частью публичного интерфейса нескольких классы.

  • Их значения могут измениться в будущих выпусках.

  • Критически важно, чтобы все реализации использовали одни и те же значения.

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

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

Ответ 8

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

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