Warum verfügt java.time über Methoden zum Erstellen von Objekten anstatt nur über Konstruktoren?

8

Im neuen Paket java.time verwenden die Kernklassen die Factory-Methode ofanstelle eines öffentlichen Konstruktors. Obwohl ich die Kosmetik der ofMethode mag, sehe ich keinen guten Grund, keinen Konstruktor zu verwenden. Die Dokumentation, die ich gefunden habe, erklärt nur, dass dies der Fall ist und geht nicht wirklich darauf ein, warum dies der Fall ist.

Zitat aus dem Tutorial :

Die meisten Klassen in der Datums- / Uhrzeit-API erstellen Objekte, die unveränderlich sind. Dies bedeutet, dass das Objekt nach dem Erstellen nicht mehr geändert werden kann. Um den Wert eines unveränderlichen Objekts zu ändern, muss ein neues Objekt als modifizierte Kopie des Originals erstellt werden. Dies bedeutet auch, dass die Datums- / Uhrzeit-API per Definition threadsicher ist. Dies wirkt sich insofern auf die API aus, als den meisten Methoden zum Erstellen von Datums- oder Zeitobjekten ein Präfix von, von oder mit anstelle von Konstruktoren vorangestellt wird und keine festgelegten Methoden vorhanden sind.

Softarn
quelle

Antworten:

9

Factory-Methoden ermöglichen die Vereinfachung der Konstruktoren - die Differenz, welche Zeitinstanz durch das Objekt dargestellt wird, kann von der Berechnung des Augenblicks getrennt werden.

Es gibt viele Factory-Methoden, einige führen String-Parsing durch, andere konvertieren von einer anderen Datums- oder Zeitdarstellung, aber alles, was passieren kann, bevor das Objekt erstellt werden muss (wenn das Erstellen eines neuen Objekts überhaupt erforderlich ist - könnte auch eine sein zwischengespeichert).

Ein weiterer großer Vorteil ist, dass Factory-Methoden beschreibende Namen haben können : Die Methoden zum Parsen von String-Darstellungen werden zB benannt LocalTime.parse- das ist mit Konstruktoren nicht möglich.

Hulk
quelle
Um diese Antwort zu verbessern, können Sie ein Beispiel aus einem Month-Objekt hinzufügen, das auswählt, welche Instanz in der of-Factory-Methode zurückgegeben werden soll.
Softarn
8

Es ist nicht immer erforderlich, ein neues Objekt zu erstellen, wenn Sie einen Wert eines unveränderlichen Typs erhalten. Eine Factory-Methode kann ein Objekt zurückgeben, das bereits erstellt wurde, während ein Konstruktoraufruf immer ein neues Objekt erstellt.

Factory-Methoden bieten einige andere Vorteile, die jedoch nicht so eng mit der Unveränderlichkeit und der Datums- / Uhrzeit-API zusammenhängen. Factory-Methoden haben beispielsweise Namen und können Objekte eines bestimmten Subtyps zurückgeben.

KOMME AUS
quelle
5

Ich habe die Datums- / Uhrzeit-API von Java nicht entworfen oder geschrieben, daher kann ich nicht definitiv sagen, warum sie das so gemacht haben. Ich kann jedoch zwei Hauptgründe vorschlagen, warum sie dies so tun sollten:

  1. Ausdruckskraft
  2. Parallele Form

Betrachten Sie zunächst den Kontext: Javas Datums- und Zeitcode ist ein großes, komplexes Paket, das sich mit einem sehr kniffligen Thema befasst. So häufig "Zeit" auch ist, die Implementierung des Konzepts umfasst Dutzende von Interpretationen und Formaten mit Variationen über geografische Standorte (Zeitzonen), Sprachen, Kalendersysteme, Uhrauflösungen, Zeitskalen, numerische Darstellungen, Datenquellen und Intervalle. Korrekturdeltas (z. B. Schaltjahre / -tage) sind häufig und können jederzeit unregelmäßig eingefügt werden (Schaltsekunden). Das Paket ist jedoch eine Kernfunktion, die wahrscheinlich weit verbreitet ist, und es wird erwartet, dass alle diese Variationen sowohl korrekt als auch präzise behandelt werden. Es ist eine große Aufgabe. Das Ergebnis ist nicht überraschend eine komplexe API mit vielen Klassen mit jeweils vielen Methoden.

Warum sollten Sie ofMethoden anstelle eines "normalen" Klassenkonstruktors verwenden? Zum Beispiel , warum LocalDate.of(...), LocalDate.ofEpochDay(...)und LocalDate.ofYearDay(...)nicht nur eine Handvoll new LocalDate(...)Anruf?

Erstens Ausdruckskraft : Einzelne benannte Methoden drücken die Absicht der Konstruktion und die Form der verbrauchten Daten aus.

Nehmen Sie für einen Moment an, dass die Überladung der Java- Methode ausreicht, um die gewünschte Vielfalt an Konstruktoren zuzulassen. (Ohne auf eine sprachliche Diskussion über Ententypisierung und Einzel- oder Dynamikversuche im Vergleich zu Mehrfachversand einzugehen, sage ich nur: Manchmal ist dies wahr, manchmal nicht. Nehmen wir vorerst nur an, dass es keine technischen Einschränkungen gibt.)

In der Konstruktordokumentation kann möglicherweise longangegeben werden, dass "wenn nur ein einziger Wert übergeben wird, dieser Wert als Anzahl der Epochentage und nicht als Jahr interpretiert wird". Wenn die an die anderen Konstruktoren übergebenen Datentypen auf niedriger Ebene unterschiedlich sind (z. B. nur der Epochenfall longund alle anderen Konstruktoren int), können Sie damit durchkommen.

Wenn Sie sich Code ansehen , der einen LocalDateKonstruktor mit einem einzelnen aufruft long, sieht er dem Code sehr ähnlich, in dem ein Jahr übergeben wird, insbesondere wenn ein Jahr wahrscheinlich der häufigste Fall ist. Es könnte möglich sein, diesen gemeinsamen Konstruktor zu haben, aber dies würde nicht unbedingt zu großer Klarheit führen.

Die API, die explizit aufruft ofEpochDay, signalisiert jedoch einen deutlichen Unterschied in Einheiten und Absichten. In ähnlicher Weise wird LocalDate.ofYearDaysignalisiert, dass der allgemeine monthParameter übersprungen wird und dass der Tagesparameter, obwohl er noch ein intist, dayOfYearkein a ist dayOfMonth. Explizite Konstruktormethoden sollen die Vorgänge klarer ausdrücken.

Dynamisch und ententypisierte Sprachen wie Python und JavaScript haben andere Mittel, um alternative Interpretationen zu signalisieren (z. B. optionale Parameter und Out-of-Band- Sentinel-Werte ), aber selbst Python- und JS-Entwickler verwenden häufig unterschiedliche Methodennamen, um unterschiedliche Interpretationen zu signalisieren. Es ist in Java aufgrund seiner technischen Einschränkungen (es ist eine statisch typisierte Single-Dispatch-Sprache) und seiner kulturellen Konventionen / Redewendungen noch häufiger.

Zweitens parallele Form . LocalDatekönnte zusätzlich zu oder anstelle seiner beiden ofMethoden einen Standard-Java-Konstruktor bereitstellen . Aber zu welchem ​​Zweck? Es braucht noch ofEpochDayund ofDayYearfür diese Anwendungsfälle. Einige Klassen bieten sowohl Konstruktoren als auch das Äquivalent von ofXYZMethoden - zum Beispiel beide Float('3.14')und Float.parseFloat('3.14')existieren. Aber wenn Sie ofXYZfür einige Dinge Konstruktoren benötigen , warum nicht diese ständig verwenden? Das ist einfacher, regelmäßiger und paralleler. Als unveränderliche Art, die Mehrheit der LocalDatesind Methoden Bauer. Es gibt sieben Hauptgruppen von Methoden , dass Konstrukt neue Instanzen (bzw. die at, from, minus, of, parse,plusund withPräfixe). Von LocalDateden über 60 Methoden sind 35 de facto Konstruktoren. Nur zwei davon konnten problemlos in klassenbasierte Standardkonstruktoren konvertiert werden. Ist es wirklich sinnvoll, diese 2 auf eine Weise zu spezialisieren, die nicht parallel zu allen anderen ist? Wäre Client-Code mit diesen beiden Sonderfallkonstruktoren deutlich klarer oder einfacher? Wahrscheinlich nicht. Dies könnte den Client-Code sogar vielfältiger und unkomplizierter machen.

Jonathan Eunice
quelle