Java-konstruktører vs statiske fabrikkmetoder

1. Oversikt

Java-konstruktører er standardmekanismen for å få fullt initialiserte klasseinstanser. Tross alt gir de all infrastrukturen som kreves for å injisere avhengigheter, enten manuelt eller automatisk.

I noen få spesifikke bruksområder er det imidlertid å foretrekke å bruke statiske fabrikkmetoder for å oppnå det samme resultatet.

I denne opplæringen vil vi fremheve fordeler og ulemper ved å bruke statiske fabrikkmetoder vs vanlige gamle Java-konstruktører.

2. Fordeler med statiske fabrikkmetoder over konstruktører

Hva kan være galt med konstruktører på et objektorientert språk som Java? Samlet sett ingenting. Likevel sier den berømte Joshua Block's Effective Java Item 1 tydelig:

“Vurder statiske fabrikkmetoder i stedet for konstruksjoner”

Selv om dette ikke er en sølvkule, er det de mest overbevisende grunnene som opprettholder denne tilnærmingen:

  1. Konstruktører har ikke meningsfulle navn, slik at de alltid er begrenset til standard navnekonvensjon som er pålagt av språket. Statiske fabrikkmetoder kan ha meningsfulle navn, derav eksplisitt formidle hva de gjør
  2. Statiske fabrikkmetoder kan returnere samme type som implementerer metoden (e), en undertype, og også primitiver, så de tilbyr et mer fleksibelt utvalg av returtyper
  3. Statiske fabrikkmetoder kan kapsle inn all logikken som kreves for å forhåndskonstruere fullstendig initialiserte forekomster, slik at de kan brukes til å flytte denne ekstra logikken ut av konstruktører. Dette forhindrer konstruktører i å utføre flere oppgaver, andre enn bare å initialisere felt
  4. Statiske fabrikkmetoder kan være kontrollerte instanser, med Singleton-mønsteret som det mest påfallende eksemplet på denne funksjonen

3. Statiske fabrikkmetoder i JDK

Det er mange eksempler på statiske fabrikkmetoder i JDK som viser mange av fordelene som er skissert ovenfor. La oss utforske noen av dem.

3.1. De String Klasse

På grunn av det velkjente String interning, det er veldig usannsynlig at vi bruker String klassekonstruktør for å lage en ny String gjenstand. Likevel er dette helt lovlig:

Strengverdi = ny streng ("Baeldung");

I dette tilfellet vil konstruktøren lage en ny String objekt, som er den forventede oppførselen.

Alternativt, hvis vi vil lage en ny String objektet ved hjelp av en statisk fabrikkmetode, kan vi bruke noen av følgende implementeringer av verdien av() metode:

Strengverdi1 = String.valueOf (1); Strengverdi2 = String.valueOf (1.0L); Strengverdi3 = String.valueOf (true); Strengverdi4 = String.valueOf ('a'); 

Det er flere overbelastede implementeringer av verdien av(). Hver og en vil returnere en ny String objekt, avhengig av typen argument som sendes til metoden (f.eks. int, lang, boolsk, røye, og så videre).

Navnet uttrykker ganske tydelig hva metoden gjør. Det holder seg også til en veletablert standard i Java-økosystemet for å navngi statiske fabrikkmetoder.

3.2. De Valgfri Klasse

Et annet pent eksempel på statiske fabrikkmetoder i JDK er Valgfri klasse. Denne klassen implementerer noen få fabrikkmetoder med ganske meningsfulle navn, gjelder også tømme(), av(), og ofNullable ():

Valgfri verdi1 = Optional.empty (); Valgfri verdi2 = Optional.of ("Baeldung"); Valgfri verdi3 = Valgfri.ofNullable (null);

3.3. De Samlinger Klasse

Ganske muligens det mest representative eksemplet på statiske fabrikkmetoder i JDK er Samlinger klasse. Dette er en ikke-øyeblikkelig klasse som kun implementerer statiske metoder.

Mange av disse er fabrikkmetoder som også returnerer samlinger, etter å ha brukt en slags algoritme på den medfølgende samlingen.

Her er noen typiske eksempler på klassens fabrikkmetoder:

Collection syncedCollection = Collections.synchronizedCollection (originalCollection); Sett syncedSet = Collections.synchronizedSet (nytt HashSet ()); List unmodifiableList = Collections.unmodifiableList (originalList); Map unmodifiableMap = Collections.unmodifiableMap (originalMap); 

Antall statiske fabrikkmetoder i JDK er veldig omfattende, så vi vil holde listen over eksempler kort for kortfattede skyld.

Likevel skal eksemplene ovenfor gi oss en klar ide om hvor allestedsnærværende statiske fabrikkmetoder er i Java.

4. Egendefinerte statiske fabrikkmetoder

Selvfølgelig, vi kan implementere våre egne statiske fabrikkmetoder. Men når er det virkelig verdt å gjøre det, i stedet for å lage klasseinstanser via vanlige konstruktører?

La oss se et enkelt eksempel.

La oss vurdere dette naive Bruker klasse:

public class User {private final Strengnavn; privat endelig streng e-post; privat finale Stringland; offentlig bruker (strengnavn, streng-e-post, strengland) {this.name = navn; this.email = e-post; this.country = land; } // standard getters / toString}

I dette tilfellet er det ingen synlige advarsler som indikerer at en statisk fabrikkmetode kan være bedre enn standardkonstruktøren.

Hva om vi vil at alt Bruker forekomster får en standardverdi for land felt?

Hvis vi initialiserer feltet med en standardverdi, må vi også omlegge konstruktøren, og dermed gjøre designet mer stivt.

Vi kan bruke en statisk fabrikkmetode i stedet:

offentlig statisk bruker createWithDefaultCountry (strengnavn, streng-e-post) {returner ny bruker (navn, e-postadresse, "Argentina"); }

Slik får vi en Bruker forekomst med en standardverdi tilordnet land felt:

Brukerbruker = User.createWithDefaultCountry ("John", "[e-postbeskyttet]");

5. Flytter logikk ut av konstruktører

Våre Bruker klasse kan raskt råtne inn i et feil design hvis vi bestemmer oss for å implementere funksjoner som vil kreve å legge til ytterligere logikk til konstruktøren (alarmklokkene skal lyde av på dette tidspunktet).

La oss anta at vi ønsker å gi klassen muligheten til å logge tidspunktet hvor hver Bruker objekt er opprettet.

Hvis vi bare legger denne logikken inn i konstruktøren, ville vi bryte prinsippet om enkelt ansvar. Vi vil ende opp med en monolitisk konstruktør som gjør mye mer enn å initialisere felt.

Vi kan holde designet rent med en statisk fabrikkmetode:

public class User {private static final Logger LOGGER = Logger.getLogger (User.class.getName ()); privat slutt Strengnavn; privat endelig streng e-post; privat finale Stringland; // standardkonstruktører / getters offentlig statisk bruker createWithLoggedInstantiationTime (strengnavn, streng e-post, strengland) {LOGGER.log (Level.INFO, "Opprette brukerinstans på: {0}", LocalTime.now ()); returner ny bruker (navn, e-postadresse, land); }} 

Slik skaper vi forbedringer Bruker forekomst:

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

6. Instansstyrt øyeblikkelig

Som vist ovenfor kan vi kapsle biter av logikk inn i statiske fabrikkmetoder før vi returnerer fullt initialisert Bruker gjenstander. Og vi kan gjøre dette uten å forurense konstruktøren med ansvaret for å utføre flere, ikke-relaterte oppgaver.

For eksempel, antar at vi ønsker å lage vår Bruker klasse en Singleton. Vi kan oppnå dette ved å implementere en instansestyrt statisk fabrikkmetode:

offentlig klasse bruker {privat statisk flyktig brukerinstans = null; // andre felt / standardkonstruktører / getters offentlig statisk bruker getSingletonInstance (strengnavn, streng e-post, strengland) {if (forekomst == null) {synkronisert (bruker.klasse) {hvis (forekomst == null) {forekomst = ny Bruker (navn, e-postadresse, land); }}} returner forekomst; }} 

Gjennomføringen av getSingletonInstance () metoden er trådsikker, med en liten ytelsesstraff på grunn av den synkroniserte blokken.

I dette tilfellet brukte vi lat initialisering for å demonstrere implementeringen av en instansestyrt statisk fabriksmetode.

Det er imidlertid verdt å nevne det den beste måten å implementere en Singleton på er med Java enum type, ettersom den både er seriøsikker og trådsikker. For fullstendig informasjon om hvordan du implementerer Singletons ved hjelp av forskjellige tilnærminger, vennligst sjekk denne artikkelen.

Som forventet, får en Bruker objekt med denne metoden ser veldig ut som de forrige eksemplene:

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

7. Konklusjon

I denne artikkelen undersøkte vi noen få brukstilfeller der statiske fabrikkmetoder kan være et bedre alternativ til bruk av vanlige Java-konstruktører.

Dessuten er dette refaktoriseringsmønsteret så tett forankret til en typisk arbeidsflyt at de fleste IDEer vil gjøre det for oss.

Apache NetBeans, IntelliJ IDEA og Eclipse vil selvsagt utføre refactoring på litt forskjellige måter, så sørg først for å sjekke IDE-dokumentasjonen.

Som med mange andre refaktoriseringsmønstre, bør vi bruke statiske fabrikkmetoder med forsiktighet, og bare når det er verdt å bytte mellom å produsere mer fleksible og rene design og kostnadene ved å måtte implementere flere metoder.

Som vanlig er alle kodeeksemplene vist i denne artikkelen tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found