Как вы определяете и используете константы в Java?
Большинство советов в Интернете содержат следующие мнения:
- Объявить
public static final
для констант в классе - Не используйте интерфейсы для констант
Наиболее распространенный способ определения константы – в классе и с использованием public static final
. Затем можно использовать константу в другом классе, используя className. ИМЯ_КОНСТАНТА
. Константы обычно определяются в верхнем регистре, как правило, по крайней мере, в Java.
Итак, если бы я определил константу для значения Pi (π), это было бы что-то вроде:
public final class Constants { public static final double PI = 3.14; }
Затем это можно использовать как Константы. PI
всякий раз, когда мы хотим сослаться на значение Pi.
Другой способ определения констант – это использование интерфейсов.
public interface Constants { double PI = 3.14; }
Однако большинство источников в Интернете не рекомендуют это делать. Почему? Потому что это анти-паттерн.
Но действительно ли это Анти-паттерн?
Давайте рассмотрим разницу, используя оба метода.
- Создание класса констант:
package constants; public final class MathConstantsClass { public static final double PI = 3.14; }
- Создание интерфейса:
package constants; public interface MathConstantsInterface { double PI = 3.14; }
Давайте определим другой интерфейс, который поможет нам протестировать оба вышеперечисленных метода.
package operations; public interface CircleArea { double calculate(double radius); }
Приведенный выше интерфейс поможет нам определить контракт для вычисления площади круга. Как мы знаем, площадь окружности зависит только от ее радиуса и, таким образом, отражается в приведенном выше интерфейсе.
Следующий класс предоставляет реализацию вычисления площади окружности.
import constants.MathConstantsClass; import operations.CircleArea; public class MathConstantsClassImplementation implements CircleArea { public double calculate(double radius) { return MathConstantsClass.PI * radius * radius; } }
Чтобы протестировать приведенный выше код, давайте напишем тестовый класс с использованием JUnit.
import operations.CircleArea; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MathConstantsClassImplementationTest { @Test void calculate() { CircleArea area = new MathConstantsClassImplementation(); double circleArea = area.calculate(1.0); assertEquals(3.14, circleArea); } }
Если вы запустите приведенный выше фрагмент тестового кода, тест пройдет успешно.
Чтобы проверить, как мы можем использовать константы с интерфейсом, давайте напишем другой класс с именем MathConstantsInterfaceImplementation
.
import constants.MathConstantsInterface; import operations.CircleArea; public class MathConstantsInterfaceImplementation implements MathConstantsInterface, CircleArea { public double calculate(double radius) { return PI * radius * radius; } }
Аналогичным образом, тест для вышеупомянутого класса выглядит следующим образом:
import operations.CircleArea; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MathConstantsInterfaceImplementationTest { @Test void calculate() { CircleArea area = new MathConstantsInterfaceImplementation(); double circleArea = area.calculate(1.0); assertEquals(3.14, circleArea); } }
Вышеуказанный тест будет пройден. Однако аргумент против реализации заключается в том, что это не очень хорошая практика, поскольку может возникнуть затенение полей, и это переопределит исходное значение константы внутри класса.
Это можно лучше понять на следующем примере:
import constants.MathConstantsInterface; import operations.CircleArea; public class MathConstantsWithInterfaceImplementationAndConstantShadowing implements MathConstantsInterface, CircleArea { private static final double PI = 200; public double calculate(double radius) { return PI * radius * radius; } }
Если бы случайно кто-то переопределил значение PI
внутри класса, это привело бы к неправильному выводу. Это можно легко проверить с помощью следующего теста.
import operations.CircleArea; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MathConstantsWithInterfaceImplementationAndConstantShadowingTest { @Test void calculate() { CircleArea area = new MathConstantsWithInterfaceImplementationAndConstantShadowing(); double circleArea = area.calculate(1.0); assertEquals(3.14, circleArea); } }
Приведенный выше тест завершается неудачей. Ответ, возвращаемый calculate()
, равен 200.0
вместо ожидаемого 3.14
. Другой аргумент заключается в том, что использование интерфейса приведет к загрязнению пространства имен, а также приведет к распространению значения по подклассам.
Приведенные выше аргументы являются обоснованными и справедливыми.
Однако о чем никто не упоминает, так это о том, что вы все равно можете напрямую использовать константы из интерфейса без реализации интерфейса. Точно так же, как в первом примере, где мы используем класс Math Constants. PI
, мы также можем использовать интерфейс математических констант . PI
без влияния на пространство имен, проблемы наследования и затенения.
Это также можно легко проверить:
import constants.MathConstantsInterface; import operations.CircleArea; public class MathConstantsInterfaceWithoutImplementation implements CircleArea { public double calculate(double radius) { return MathConstantsInterface.PI * radius * radius; } }
Тестовый класс:
import operations.CircleArea; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MathConstantsInterfaceWithoutImplementationTest { @Test void calculate() { CircleArea area = new MathConstantsInterfaceWithoutImplementation(); double circleArea = area.calculate(1.0); assertEquals(3.14, circleArea); } }
Это не имело бы никакого значения для нашего способа реализации. Даже количество импорта остается прежним. Более того, вам не нужен дополнительный шаблон public static final
, поскольку элементы интерфейса по умолчанию являются public static final
.
public static final double PI = 3.14;
против
double PI = 3.14;
Что бы вы предпочли? Более чистый код, кто-нибудь?
Я видел, что большинство констант почти сгруппированы вместе, если они используются во всем приложении. Вы также могли бы предложить, чтобы интерфейс использовался только для контрактов, и в большинстве случаев так оно и есть. Однако сохранение интерфейса исключительно для хранения констант мне тоже не кажется неправильным!
Если, конечно, какой-нибудь разработчик не попытается реализовать класс, который содержит исключительно константы – что породило бы вопрос – ПОЧЕМУ?
Вы можете ознакомиться с кодом на моем Github: Вы можете ознакомиться с кодом на моем Github:
Рекомендации:
Оригинал: “https://dev.to/darshitpp/the-java-constants-interface-anti-pattern-3cj6”