Интерфейсы со статическими полями в Java для совместного использования "констант"
Я смотрю на некоторые проекты Java с открытым исходным кодом, чтобы войти в Java, и заметил, что многие из них имеют своего рода интерфейс "констант".
Например, http://www.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 ответов
Это обычно считается плохой практикой. Проблема заключается в том, что константы являются частью общедоступного "интерфейса" (если не сказать лучшего слова) класса реализации. Это означает, что реализующий класс публикует все эти значения во внешних классах, даже если они требуются только для внутреннего использования. Константы распространяются по всему коду. Примером является интерфейс SwingConstants в Swing, который реализуется десятками классов, которые все "реэкспортируют" все свои константы (даже те, которые они не используют) как свои собственные.
Но не просто поверьте мне на слово, Джош Блох также говорит, что это плохо:
Постоянный шаблон интерфейса - плохое использование интерфейсов. То, что класс использует некоторые константы внутри, является деталью реализации. Реализация постоянного интерфейса вызывает утечку этой детали реализации в экспортируемый 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 должен быть изменен, но...
Константы не должны быть изменены, потому что колесо круглое, а двигатель механический, но... В будущем инженеры 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 на уровне абстракции, не затрагивая реализации
Это не безопасно, я знаю, но я все еще хочу знать, что вы думаете об этом
В 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" является непонятным именем, вы можете изменить его на "WithinDistanceInKm" и знать, что новый синтаксис будет соблюдаться всеми вашими реализациями индекса.
PS: вдохновение для этого примера взято из пространственного кода Neo4j.
Учитывая преимущество задним числом, мы видим, что 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 в интерфейсах часто является частью хорошего обходного пути.
Согласно спецификации JVM, поля и методы в интерфейсе могут иметь только Public, Static, Final и Abstract. Ссылка изнутри Java VM
По умолчанию все методы интерфейса являются абстрактными, даже если вы не упомянули об этом явно.
Интерфейсы предназначены только для спецификации. Он не может содержать никаких реализаций. Таким образом, чтобы избежать реализации классов для изменения спецификации, она делается окончательной. Поскольку интерфейс не может быть создан, они становятся статическими для доступа к полю с использованием имени интерфейса.
У меня недостаточно репутации, чтобы комментировать Плерок, поэтому я должен создать ответ. Я прошу прощения за это, но он приложил немало усилий, и я хотел бы ответить ему.
Плерок, вы создали идеальный пример, чтобы показать, почему эти константы должны быть независимыми от интерфейсов и независимыми от наследования. Для клиента приложения не важно, что есть техническая разница между этими реализациями автомобилей. Они одинаковы для клиента, только автомобили. Итак, клиент хочет взглянуть на них с той точки зрения, который представляет собой интерфейс, подобный I_Somecar. На протяжении всего приложения клиент будет использовать только одну перспективу, а не разные для каждой марки автомобиля.
Если клиент хочет сравнить автомобили перед покупкой, у него может быть такой метод:
public List<Decision> compareCars(List<I_Somecar> pCars);
Интерфейс представляет собой договор о поведении и показывает различные объекты с одной точки зрения. Как бы вы его ни проектировали, у каждой автомобильной марки будет своя линия наследования. Хотя это на самом деле совершенно правильно, потому что автомобили могут быть настолько разными, что это может быть похоже на сравнение совершенно разных типов объектов, в конце концов, есть выбор между разными автомобилями. И это перспектива интерфейса, которым должны обладать все бренды. Выбор констант не должен сделать это невозможным. Пожалуйста, рассмотрите ответ Зарконнена.
Это произошло за время до появления Java 1.5 и принесло нам перечисления. До этого не было хорошего способа определить набор констант или ограниченных значений.
Это все еще используется, в большинстве случаев, либо для обратной совместимости, либо из-за большого количества рефакторинга, необходимого для избавления, во многих проектах.