Konstanten in Java: Muster und Anti-Muster

1. Einleitung

In diesem Artikel lernen wir die Verwendung von Konstanten in Java kennen, wobei der Schwerpunkt auf allgemeinen Mustern und Anti-Mustern liegt.

Wir beginnen mit einigen grundlegenden Konventionen zum Definieren von Konstanten. Von dort aus werden wir uns mit gängigen Anti-Mustern befassen, bevor wir uns mit gängigen Mustern befassen.

2. Grundlagen

Eine Konstante ist eine Variable, deren Wert sich nach ihrer Definition nicht ändert.

Schauen wir uns die Grundlagen zum Definieren einer Konstante an:

private static final int OUR_CONSTANT = 1;

Einige der Muster, die wir betrachten werden, befassen sich mit der Entscheidung über den Modifikator für den öffentlichen oder privaten Zugriff. Wir machen unsere Konstanten statisch und endgültig und geben ihnen einen geeigneten Typ, egal ob es sich um ein Java-Grundelement, eine Klasse oder eine Aufzählung handelt . Der Name sollte aus Großbuchstaben bestehen, wobei die Wörter durch Unterstriche getrennt sind , was manchmal als schreiender Schlangenfall bezeichnet wird. Schließlich liefern wir den Wert selbst.

3. Anti-Patterns

Lassen Sie uns zunächst lernen, was nicht zu tun ist. Schauen wir uns einige gängige Anti-Patterns an, die bei der Arbeit mit Java-Konstanten auftreten können.

3.1. Magische Zahlen

Magische Zahlen sind numerische Literale in einem Codeblock:

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

Sie sind für andere Entwickler schwer zu verstehen. Wenn wir im gesamten Code eine Zahl verwenden, ist es außerdem schwierig, den Wert zu ändern. Wir sollten stattdessen die Zahl als Konstante definieren.

3.2. Eine große globale Konstantenklasse

Wenn wir ein Projekt starten, kann es natürlich sein, eine Klasse mit dem Namen Constants oder Utils zu erstellen, um dort alle Konstanten für die Anwendung zu definieren. Für kleinere Projekte mag dies in Ordnung sein, aber lassen Sie uns einige Gründe betrachten, warum dies keine ideale Lösung ist.

Stellen wir uns zunächst vor, wir haben hundert oder mehr Konstanten in unserer Konstantenklasse. Wenn die Klasse nicht gepflegt wird, um sowohl mit der Dokumentation Schritt zu halten als auch die Konstanten gelegentlich in logische Gruppierungen umzugestalten, wird sie ziemlich unlesbar. Wir könnten sogar doppelte Konstanten mit leicht unterschiedlichen Namen erhalten. Dieser Ansatz führt wahrscheinlich zu Problemen mit der Lesbarkeit und Wartbarkeit bei allen anderen Projekten als den kleinsten.

Neben der Logistik für die Wartung der Konstantenklasse selbst führen wir auch zu anderen Problemen bei der Wartbarkeit, indem wir zu viele Abhängigkeiten mit dieser einen globalen Konstantenklasse und verschiedenen anderen Teilen unserer Anwendung fördern.

Auf einer technischeren Seite platziert der Java-Compiler den Wert der Konstante in referenzierenden Variablen in den Klassen, in denen wir sie verwenden . Wenn wir also eine unserer Konstanten in unserer Konstantenklasse ändern und nur diese Klasse und nicht die referenzierende Klasse neu kompilieren, können wir inkonsistente Konstantenwerte erhalten.

3.3. Das Anti-Pattern der konstanten Schnittstelle

Das konstante Schnittstellenmuster ist, wenn wir eine Schnittstelle definieren, die alle Konstanten für bestimmte Funktionen enthält und dann über die Klassen verfügt, die diese Funktionen zur Implementierung der Schnittstelle benötigen.

Definieren wir eine konstante Schnittstelle für einen Taschenrechner:

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

Als nächstes implementieren wir unsere CalculatorConstants- Schnittstelle:

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

Das erste Argument gegen die Verwendung einer konstanten Schnittstelle ist, dass dies gegen den Zweck einer Schnittstelle verstößt. Wir sollen Schnittstellen verwenden, um einen Vertrag für das Verhalten zu erstellen, das unsere implementierenden Klassen bereitstellen werden. Wenn wir eine Schnittstelle voller Konstanten erstellen, definieren wir kein Verhalten.

Zweitens öffnet uns die Verwendung einer konstanten Schnittstelle für Laufzeitprobleme, die durch Feldschatten verursacht werden. Schauen wir uns an, wie dies passieren kann, indem wir eine UPPER_LIMIT- Konstante in unserer GeometryCalculator- Klasse definieren:

public static final double UPPER_LIMIT = 100000000000000000000.0;

Sobald wir diese Konstante in unserer GeometryCalculator- Klasse definiert haben, verbergen wir den Wert in der CalculatorConstants- Schnittstelle für unsere Klasse. Wir könnten dann unerwartete Ergebnisse erzielen.

Ein weiteres Argument gegen dieses Anti-Muster ist, dass es eine Verschmutzung des Namespace verursacht. Unsere CalculatorConstants befinden sich jetzt im Namespace für alle unsere Klassen, die die Schnittstelle implementieren, sowie für jede ihrer Unterklassen.

4. Muster

Zuvor haben wir uns die geeignete Form zum Definieren von Konstanten angesehen. Schauen wir uns einige andere bewährte Methoden zum Definieren von Konstanten in unseren Anwendungen an.

4.1. Allgemeine gute Praktiken

Wenn Konstanten logisch mit einer Klasse verknüpft sind, können wir sie einfach dort definieren. Wenn wir eine Reihe von Konstanten als Elemente eines Aufzählungstyps anzeigen, können wir sie mit einer Aufzählung definieren.

Definieren wir einige Konstanten in einer Calculator- Klasse:

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; } }

In unserem Beispiel haben wir eine Konstante für UPPER_LIMIT definiert , die wir nur in der Calculator- Klasse verwenden möchten. Daher haben wir sie auf privat gesetzt . Wir möchten, dass andere Klassen PI und die Operation enum verwenden können, daher haben wir diese öffentlich gemacht .

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 um sie zu definieren.

Wir können sehen, dass sich unsere spezifische Konstantenklasse auf Paketebene von einer großen globalen Konstantenklasse unterscheidet, da sie in unserem Paket lokalisiert ist und Konstanten enthält, die für die Klassen dieses Pakets relevant sind.

5. Schlussfolgerung

In diesem Artikel haben wir die Vor- und Nachteile einiger der beliebtesten Muster und Anti-Muster betrachtet, die bei der Verwendung von Konstanten in Java auftreten. Wir haben mit einigen grundlegenden Formatierungsregeln begonnen, bevor wir uns mit Anti-Mustern befasst haben. Nachdem wir einige gängige Anti-Muster kennengelernt hatten, haben wir uns Muster angesehen, die häufig auf Konstanten angewendet werden.

Wie immer ist der Code auf GitHub verfügbar.