Константи в Java: Модели и анти-модели

1. Въведение

В тази статия ще научим за използването на константи в Java с фокус върху общи модели и анти-модели.

Ще започнем с някои основни конвенции за дефиниране на константи. Оттам ще преминем към често срещани анти-модели, преди да завършим с поглед към често срещаните модели.

2. Основни положения

Константа е променлива, чиято стойност няма да се промени, след като бъде дефинирана.

Нека да разгледаме основите за дефиниране на константа:

private static final int OUR_CONSTANT = 1;

Някои от моделите, които ще разгледаме, ще се отнасят до решението за модификация на публичния или частния достъп. Ние правим нашите константи статични и окончателни и им даваме подходящ тип, независимо дали това е Java примитив, клас или преброяване . Името трябва да бъде с всички главни букви с думите, разделени с долни черти , понякога известни като пищящ змийски случай. И накрая, ние предоставяме самата стойност.

3. Анти-модели

Първо, нека започнем, като се научим какво да не правим. Нека разгледаме няколко често срещани анти-шаблони, които може да срещнем при работа с Java константи.

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

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

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

Те са трудни за разбиране от други разработчици. Освен това, ако използваме число в целия си код, е трудно да се справим с промяната на стойността. Вместо това трябва да определим числото като константа.

3.2. Голям клас на глобални константи

Когато стартираме проект, може да се почувства естествено да създадем клас на име Constants или Utils с намерението да дефинираме всички константи за приложението там. За по-малки проекти това може да е добре, но нека разгледаме няколко причини, поради които това не е идеалното решение.

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

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

От по-техническа страна, 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. Общи добри практики

Ако константите са логически свързани с клас, можем просто да ги дефинираме там. Ако разглеждаме набор от константи като членове на изброен тип, можем да използваме enum, за да ги дефинираме.

Нека дефинираме някои константи в клас Калкулатор :

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 , която планираме да използваме само в класа Калкулатор , така че я зададохме като частна . Искаме други класове да могат да използват PI и Operation enum, затова сме задали тези за обществени .

Let's consider some of the advantages of using an enum for Operation. The first advantage is that it limits the possible values. Imagine that our method takes a string for the operation value with the expectation that one of four constant strings is supplied. We can easily foresee a scenario where a developer calling the method sends their own string value. With the enum, the values are limited to those we define. We can also see that enums are especially well suited to use in switch statements.

4.2. Constants Class

Now that we've looked at some general good practices, let's consider the case when a constants class might be a good idea. Let's imagine our application contains a package of classes that need to do various kinds of mathematical calculations. In this case, it probably makes sense for us to define a constants class in that package for constants that we'll use in our calculations classes.

Let's create a MathConstants class:

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() { } }

The first thing we should notice is that our class is final to prevent it from being extended. Additionally, we've defined a private constructor so it can't be instantiated. Finally, we can see that we've applied the other good practices we discussed earlier in the article. Our constant PI is public because we anticipate needing to access it outside of our package. The other constants we've left as package-private, so we can access them within our package. We've made all of our constants static and final and named them in a screaming snake case. The operations are a specific set of values, so we've used an enum да ги дефинира.

Виждаме, че нашият специфичен клас константи на ниво пакет се различава от големия глобален клас константи, защото е локализиран в нашия пакет и съдържа константи, свързани с класовете на този пакет.

5. Заключение

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

Както винаги кодът е достъпен в GitHub.