Java-constructeurs versus statische fabrieksmethoden

1. Overzicht

Java-constructors zijn het standaardmechanisme voor het verkrijgen van volledig geïnitialiseerde klasse-instanties. Ze bieden immers alle infrastructuur die nodig is om afhankelijkheden handmatig of automatisch te injecteren.

Toch verdient het in een paar specifieke gebruikssituaties de voorkeur om gebruik te maken van statische fabrieksmethoden om hetzelfde resultaat te bereiken.

In deze tutorial zullen we de voor- en nadelen van het gebruik van statische fabrieksmethoden versus gewone oude Java-constructeurs.

2. Voordelen van statische fabrieksmethoden ten opzichte van constructeurs

Wat is er mis met constructors in een objectgeoriënteerde taal als Java? Over het algemeen niets. Toch vermeldt het beroemde Effective Java Item 1 van Joshua Block duidelijk:

"Overweeg statische fabrieksmethoden in plaats van constructeurs"

Hoewel dit geen wondermiddel is, zijn hier de meest dwingende redenen die deze aanpak ondersteunen:

  1. Constructeurs hebben geen betekenisvolle namen, dus ze zijn altijd beperkt tot de standaard naamgevingsconventie die door de taal wordt opgelegd. Statische fabrieksmethoden kunnen betekenisvolle namen hebben, en dus expliciet aangeven wat ze doen
  2. Statische fabrieksmethoden kunnen hetzelfde type retourneren dat de methode (n), een subtype en ook primitieven implementeert, dus ze bieden een flexibeler aanbod van retourtypes
  3. Statische fabrieksmethoden kunnen alle logica bevatten die nodig is voor het pre-construeren van volledig geïnitialiseerde instanties, dus ze kunnen worden gebruikt om deze extra logica uit constructors te halen. Dit voorkomt dat constructeurs andere taken uitvoeren dan alleen het initialiseren van velden
  4. Statische fabrieksmethoden kunnen gecontroleerde methoden zijn, waarbij het Singleton-patroon het meest in het oog springende voorbeeld van deze functie is

3. Statische fabrieksmethoden in de JDK

Er zijn tal van voorbeelden van statische fabrieksmethoden in de JDK die veel van de hierboven beschreven voordelen laten zien. Laten we er een paar bekijken.

3.1. De Draad Klasse

Vanwege het bekende Draad stage lopen, is het zeer onwaarschijnlijk dat we de Draad class constructor om een ​​nieuw Draad voorwerp. Toch is dit volkomen legaal:

Stringwaarde = nieuwe String ("Baeldung");

In dit geval zal de constructor een nieuw Draad object, wat het verwachte gedrag is.

Als alternatief, als we dat willen maak een nieuw Draad object met behulp van een statische fabrieksmethode, kunnen we enkele van de volgende implementaties van het waarde van() methode:

String waarde1 = String.valueOf (1); Stringwaarde2 = String.valueOf (1.0L); String value3 = String.valueOf (true); String value4 = String.valueOf ('a'); 

Er zijn verschillende overbelaste implementaties van waarde van(). Elk zal een nieuwe retourneren Draad object, afhankelijk van het type argument dat aan de methode is doorgegeven (bijv. int, lang, boolean, char, enzovoorts).

De naam drukt vrij duidelijk uit wat de methode doet. Het houdt ook vast aan een gevestigde standaard in het Java-ecosysteem voor het benoemen van statische fabrieksmethoden.

3.2. De Optioneel Klasse

Een ander mooi voorbeeld van statische fabrieksmethoden in de JDK is de Optioneel klasse. Deze klas implementeert een paar fabrieksmethoden met behoorlijk betekenisvolle namen, inclusief leeg(), van(), en ofNullable ():

Optionele waarde1 = Optioneel.empty (); Facultatieve waarde2 = Facultatief.of ("Baeldung"); Optionele waarde3 = Optioneel.ofNullable (null);

3.3. De Collecties Klasse

Heel waarschijnlijk het meest representatieve voorbeeld van statische fabrieksmethoden in de JDK is de Collecties klasse. Dit is een niet-instantibele klasse die alleen statische methoden implementeert.

Veel van dit zijn fabrieksmethoden die ook verzamelingen retourneren, nadat ze op de geleverde verzameling een soort algoritme hebben toegepast.

Hier zijn enkele typische voorbeelden van de fabrieksmethoden van de klasse:

Collectie syncedCollection = Collections.synchronizedCollection (originalCollection); Set syncedSet = Collections.synchronizedSet (nieuwe HashSet ()); Lijst unmodifiableList = Collections.unmodifiableList (originalList); Map unmodifiableMap = Collections.unmodifiableMap (originalMap); 

Het aantal statische fabrieksmethoden in de JDK is erg uitgebreid, dus we zullen de lijst met voorbeelden kort houden.

Desalniettemin zouden de bovenstaande voorbeelden ons een duidelijk idee moeten geven van hoe alomtegenwoordige statische fabrieksmethoden in Java zijn.

4. Aangepaste statische fabrieksmethoden

Natuurlijk, we kunnen onze eigen statische fabrieksmethoden implementeren. Maar wanneer is het echt de moeite waard om dit te doen, in plaats van klasse-instanties te maken via gewone constructors?

Laten we een eenvoudig voorbeeld bekijken.

Laten we dit naïef vinden Gebruiker klasse:

openbare klasse Gebruiker {privé laatste String naam; privé laatste String e-mail; privé finale String land; openbare gebruiker (tekenreeksnaam, tekenreeks-e-mailadres, tekenreeksland) {this.name = naam; this.email = e-mail; this.country = land; } // standard getters / toString}

In dit geval zijn er geen zichtbare waarschuwingen om aan te geven dat een statische fabrieksmethode beter zou kunnen zijn dan de standaardconstructor.

Wat als we dat allemaal willen Gebruiker instanties krijgen een standaardwaarde voor de land veld?

Als we het veld initialiseren met een standaardwaarde, zouden we de constructor ook moeten refactoren, waardoor het ontwerp stijver wordt.

We kunnen in plaats daarvan een statische fabrieksmethode gebruiken:

openbare statische gebruiker createWithDefaultCountry (tekenreeksnaam, tekenreeks e-mail) {retourneer nieuwe gebruiker (naam, e-mailadres, "Argentinië"); }

Hier is hoe we een Gebruiker instantie met een standaardwaarde toegewezen aan de land veld:

User user = User.createWithDefaultCountry ("John", "[email protected]");

5. Logica uit constructeurs halen

Onze Gebruiker class zou snel in een gebrekkig ontwerp kunnen rotten als we besluiten om functies te implementeren waarvoor extra logica aan de constructor moet worden toegevoegd (tegen die tijd zouden er alarmbellen moeten klinken).

Laten we aannemen dat we de klas de mogelijkheid willen geven om de tijd te loggen waarop elke Gebruiker object is gemaakt.

Als we deze logica gewoon in de constructor stoppen, breken we het Single Responsibility Principle. We zouden eindigen met een monolithische constructor die veel meer doet dan alleen velden initialiseren.

We kunnen ons ontwerp schoon houden met een statische fabrieksmethode:

openbare klasse Gebruiker {privé statische laatste Logger LOGGER = Logger.getLogger (User.class.getName ()); private laatste String naam; privé laatste String e-mail; privé finale String land; // standard constructors / getters public static User createWithLoggedInstantiationTime (String naam, String e-mail, String land) {LOGGER.log (Level.INFO, "Creating User instance at: {0}", LocalTime.now ()); nieuwe gebruiker retourneren (naam, e-mailadres, land); }} 

Hier is hoe we onze verbeterde Gebruiker voorbeeld:

User user = User.createWithLoggedInstantiationTime ("John", "[email protected]", "Argentina");

6. Instantie-gecontroleerde instantiatie

Zoals hierboven getoond, kunnen we stukjes logica inkapselen in statische fabrieksmethoden voordat we volledig geïnitialiseerd terugkeren Gebruiker voorwerpen. En we kunnen dit doen zonder de constructeur te vervuilen met de verantwoordelijkheid om meerdere, niet-gerelateerde taken uit te voeren.

Bijvoorbeeld, stel dat we onze willen maken Gebruiker klasse een Singleton. We kunnen dit bereiken door een instantiegestuurde statische fabrieksmethode te implementeren:

openbare klasse Gebruiker {privé statische vluchtige gebruikersinstantie = null; // andere velden / standaard constructors / getters public static User getSingletonInstance (String name, String email, String country) {if (instance == null) {synchronized (User.class) {if (instance == null) {instance = new Gebruiker (naam, e-mail, land); }}} terugkeerinstantie; }} 

De implementatie van het getSingletonInstance () methode is thread-safe, met een kleine prestatieverbinding, vanwege het gesynchroniseerde blok.

In dit geval hebben we luie initialisatie gebruikt om de implementatie van een instantie-gecontroleerde statische fabrieksmethode te demonstreren.

Het is echter de moeite waard om dat te vermelden de beste manier om een ​​Singleton te implementeren is met een Java opsomming type, omdat het zowel serialiseringsveilig als threadveilig is. Raadpleeg dit artikel voor de volledige details over het implementeren van Singletons met verschillende benaderingen.

Zoals verwacht, krijgt u een Gebruiker object met deze methode lijkt erg op de vorige voorbeelden:

User user = User.getSingletonInstance ("John", "[email protected]", "Argentina");

7. Conclusie

In dit artikel hebben we enkele gebruiksscenario's onderzocht waarin statische fabrieksmethoden een beter alternatief kunnen zijn voor het gebruik van gewone Java-constructors.

Bovendien is dit herstructureringspatroon zo stevig geworteld in een typische workflow dat de meeste IDE's het voor ons zullen doen.

Natuurlijk zullen Apache NetBeans, IntelliJ IDEA en Eclipse de refactoring op enigszins verschillende manieren uitvoeren, dus controleer eerst uw IDE-documentatie.

Zoals bij veel andere refactoringpatronen, moeten we de statische fabrieksmethoden met de nodige voorzichtigheid gebruiken, en alleen als het de moeite waard is om een ​​afweging te maken tussen het produceren van meer flexibele en schone ontwerpen en de kosten van het implementeren van aanvullende methoden.

Zoals gewoonlijk zijn alle codevoorbeelden die in dit artikel worden getoond, beschikbaar op GitHub.