Как вы определяете и используете константы в 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”