Перейти к основному содержимому

Константы в Java: шаблоны и анти-шаблоны

· 7 мин. чтения

1. Введение

В этой статье мы узнаем об использовании констант в Java с акцентом на общие шаблоны и анти-шаблоны.

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

2. Основы

Константа — это переменная, значение которой не изменится после ее определения.

Давайте рассмотрим основы определения константы:

private static final int OUR_CONSTANT = 1;

Некоторые из шаблонов, которые мы рассмотрим, касаются решения о модификаторе доступа public или private . Мы делаем наши константы статическими и окончательными и присваиваем им соответствующий тип, будь то примитив Java, класс или перечисление . Имя должно состоять из заглавных букв со словами, разделенными символом подчеркивания , что иногда называют регистром кричащей змеи. Наконец, мы предоставляем само значение. `` ****

3. Антипаттерны

Во-первых, давайте начнем с того, что не должны делать. Давайте рассмотрим пару распространенных антишаблонов, с которыми мы можем столкнуться при работе с константами Java.

3.1. Магические числа

Магические числа — это числовые литералы в блоке кода:

if (number == 3.14159265359) {
// ...
}

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

3.2. Большой класс глобальных констант

Когда мы начинаем проект, может показаться естественным создать класс с именем Constants или Utils с намерением определить в нем все константы для приложения. Для небольших проектов это может быть нормально, но давайте рассмотрим несколько причин, почему это не идеальное решение.

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

В дополнение к логистике поддержки самого класса Constants , мы также создаем другие проблемы с ремонтопригодностью, поощряя слишком большую взаимозависимость с этим одним глобальным классом констант и различными другими частями нашего приложения.

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

3.3. Постоянный анти-шаблон интерфейса

Константный шаблон интерфейса — это когда мы определяем интерфейс, который содержит все константы для определенных функций, а затем у нас есть классы, которым нужны эти функции для реализации интерфейса.

Определим постоянный интерфейс для калькулятора:

public interface CalculatorConstants {
double PI = 3.14159265359;
double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE};
}

Далее мы реализуем наш интерфейс CalculatorConstants :

public class GeometryCalculator implements CalculatorConstants {    
public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
// Code to do an operation
}
}

Первый аргумент против использования постоянного интерфейса заключается в том, что он противоречит назначению интерфейса. Мы должны использовать интерфейсы для создания контракта на поведение, которое будут обеспечивать наши реализующие классы. Когда мы создаем интерфейс, полный констант, мы не определяем никакого поведения.

Во-вторых, использование постоянного интерфейса открывает перед нами проблемы во время выполнения, вызванные затенением полей. Давайте посмотрим, как это может произойти, определив константу UPPER_LIMIT в нашем классе GeometryCalculator :

public static final double UPPER_LIMIT = 100000000000000000000.0;

Как только мы определим эту константу в нашем классе GeometryCalculator , мы скроем значение в интерфейсе CalculatorConstants для нашего класса. Тогда мы могли бы получить неожиданные результаты.

Еще один аргумент против этого анти-шаблона заключается в том, что он вызывает загрязнение пространства имен. Наши CalculatorConstants теперь будут в пространстве имен для любого из наших классов, которые реализуют интерфейс, а также для любого из их подклассов.

4. Узоры

Ранее мы рассмотрели подходящую форму для определения констант. Давайте рассмотрим некоторые другие хорошие практики определения констант в наших приложениях.

4.1. Общие передовые методы

Если константы логически связаны с классом, мы можем просто определить их там. Если мы рассматриваем набор констант как членов перечисляемого типа, мы можем использовать перечисление для их определения.

Давайте определим некоторые константы в классе Calculator :

public class Calculator {
public static final double PI = 3.14159265359;
private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}

public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
if (numberOne > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberOne' is too large");
}
if (numberTwo > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberTwo' is too large");
}
double answer = 0;

switch(operation) {
case ADD:
answer = numberOne + numberTwo;
break;
case SUBTRACT:
answer = numberOne - numberTwo;
break;
case DIVIDE:
answer = numberOne / numberTwo;
break;
case MULTIPLY:
answer = numberOne * numberTwo;
break;
}

return answer;
}
}

В нашем примере мы определили константу для UPPER_LIMIT , которую планируем использовать только в классе Calculator , поэтому мы установили для нее значение private . Мы хотим, чтобы другие классы могли использовать PI и перечисление Operation , поэтому мы сделали их общедоступными .

Давайте рассмотрим некоторые преимущества использования перечисления для операции . Первое преимущество заключается в том, что оно ограничивает возможные значения. Представьте, что наш метод принимает строку в качестве значения операции, ожидая, что будет предоставлена одна из четырех строк-констант. Мы можем легко предвидеть сценарий, когда разработчик, вызывающий метод, отправляет собственное строковое значение. В enum значения ограничены теми, которые мы определяем. Мы также видим, что перечисления особенно хорошо подходят для использования в операторах switch .

4.2. Класс констант

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

Давайте создадим класс MathConstants :

public final class MathConstants {
public static final double PI = 3.14159265359;
static final double GOLDEN_RATIO = 1.6180;
static final double GRAVITATIONAL_ACCELERATION = 9.8;
static final double EULERS_NUMBER = 2.7182818284590452353602874713527;

public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}

private MathConstants() {

}
}

Первое, что мы должны заметить, это то, что наш класс является окончательным , чтобы предотвратить его расширение . Кроме того, мы определили частный конструктор, поэтому его нельзя создать. Наконец, мы можем видеть, что мы применили другие передовые методы, которые мы обсуждали ранее в статье. Наш постоянный PI является общедоступным , потому что мы ожидаем, что нам понадобится доступ к нему за пределами нашего пакета. Другие константы мы оставили как package-private , поэтому мы можем получить к ним доступ внутри нашего пакета. Мы сделали все наши константы статическими и окончательными и назвали их в случае кричащей змеи. Операции представляют собой определенный набор значений, поэтому мы использовали перечисление чтобы определить их.

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

5. Вывод

В этой статье мы рассмотрели плюсы и минусы некоторых наиболее популярных паттернов и анти-паттернов, наблюдаемых при использовании констант в Java. Мы начали с некоторых основных правил форматирования, прежде чем перейти к анти-шаблонам. Узнав о паре распространенных антишаблонов, мы рассмотрели шаблоны, которые часто применяются к константам.

Как всегда, код доступен на GitHub .