Java-konstruktører vs statiske fabriksmetoder

1. Oversigt

Java-konstruktører er standardmekanismen til at få fuldt initialiserede klasseinstanser. Når alt kommer til alt leverer de al infrastruktur, der kræves til injektion af afhængigheder, enten manuelt eller automatisk.

Alligevel er det i nogle få specifikke brugssager at foretrække at ty til statiske fabriksmetoder for at opnå det samme resultat.

I denne vejledning fremhæver vi fordele og ulemper ved at bruge statiske fabriksmetoder vs almindelige gamle Java-konstruktører.

2. Fordele ved statiske fabriksmetoder i forhold til konstruktører

Hvad kan der være galt med konstruktører på et objektorienteret sprog som Java? Alt i alt intet. Alligevel siger den berømte Joshua Blocks Effective Java Item 1 klart:

"Overvej statiske fabriksmetoder i stedet for konstruktører"

Selvom dette ikke er en sølvkugle, er der de mest overbevisende grunde, der opretholder denne tilgang:

  1. Konstruktører har ikke meningsfulde navne, så de er altid begrænset til den almindelige navngivningskonvention, der er pålagt af sproget. Statiske fabriksmetoder kan have meningsfulde navne, derfor udtrykkeligt formidle, hvad de gør
  2. Statiske fabriksmetoder kan returnere den samme type, der implementerer metoden (e), en undertype og også primitiver, så de tilbyder et mere fleksibelt udvalg af returnerende typer
  3. Statiske fabriksmetoder kan indkapsle al den logik, der kræves til forudkonstruktion af fuldt initialiserede forekomster, så de kan bruges til at flytte denne ekstra logik ud af konstruktører. Dette forhindrer konstruktører i at udføre yderligere opgaver, andre end blot initialisering af felter
  4. Statiske fabriksmetoder kan være kontrollerede instanser, med Singleton-mønsteret som det mest iøjnefaldende eksempel på denne funktion

3. Statiske fabriksmetoder i JDK

Der er masser af eksempler på statiske fabriksmetoder i JDK, der viser mange af de fordele, der er skitseret ovenfor. Lad os udforske nogle af dem.

3.1. Det Snor Klasse

På grund af det velkendte Snor interning, det er meget usandsynligt, at vi bruger Snor klassekonstruktør til at oprette en ny Snor objekt. Alligevel er dette helt lovligt:

Strengværdi = ny streng ("Baeldung");

I dette tilfælde opretter konstruktøren en ny Snor objekt, som er den forventede adfærd.

Alternativt, hvis vi vil oprette en ny Snor objekt ved hjælp af en statisk fabriksmetode, kan vi bruge nogle af følgende implementeringer af Værdi af() metode:

Strengværdi1 = String.valueOf (1); Strengværdi2 = String.valueOf (1.0L); Strengværdi3 = String.valueOf (sand); Strengværdi4 = String.valueOf ('a'); 

Der er flere overbelastede implementeringer af Værdi af(). Hver enkelt returnerer en ny Snor objekt, afhængigt af typen af ​​argumentet, der sendes til metoden (f.eks. int, lang, boolsk, char, og så videre).

Navnet udtrykker ret tydeligt, hvad metoden gør. Det holder sig også til en veletableret standard i Java-økosystemet til navngivning af statiske fabriksmetoder.

3.2. Det Valgfri Klasse

Et andet pænt eksempel på statiske fabriksmetoder i JDK er Valgfri klasse. Denne klasse implementerer et par fabriksmetoder med ret meningsfulde navne, inklusive tom(), af()og ofNullable ():

Valgfri værdi1 = Optional.empty (); Valgfri værdi2 = Optional.of ("Baeldung"); Valgfri værdi3 = Optional.ofNullable (null);

3.3. Det Samlinger Klasse

Ganske muligvis det mest repræsentative eksempel på statiske fabriksmetoder i JDK er Samlinger klasse. Dette er en ikke-instantierbar klasse, der kun implementerer statiske metoder.

Mange af disse er fabriksmetoder, der også returnerer samlinger efter at have anvendt en slags algoritme på den medfølgende samling.

Her er nogle typiske eksempler på klassens fabriksmetoder:

Samling syncedCollection = Collections.synchronizedCollection (originalCollection); Indstil syncedSet = Collections.synchronizedSet (nyt HashSet ()); Liste unmodifiableList = Collections.unmodifiableList (originalList); Kort unmodifiableMap = Collections.unmodifiableMap (originalMap); 

Antallet af statiske fabriksmetoder i JDK er virkelig omfattende, så vi holder listen over eksempler kort for kortfattethedens skyld.

Ikke desto mindre bør ovenstående eksempler give os en klar idé om, hvor allestedsnærværende statiske fabriksmetoder er i Java.

4. Tilpassede statiske fabriksmetoder

Selvfølgelig, vi kan implementere vores egne statiske fabriksmetoder. Men hvornår er det virkelig værd at gøre det i stedet for at oprette klasseinstanser via almindelige konstruktører?

Lad os se et simpelt eksempel.

Lad os overveje dette naive Bruger klasse:

offentlig klasse bruger {privat endelig strengnavn; privat endelig streng-mail; privat endelig String land; offentlig bruger (strengnavn, streng-e-mail, strengland) {this.name = navn; this.email = e-mail; this.country = land; } // standard getters / toString}

I dette tilfælde er der ingen synlige advarsler, der indikerer, at en statisk fabriksmetode kunne være bedre end standardkonstruktøren.

Hvad hvis vi vil have det hele Bruger forekomster får en standardværdi for Land Mark?

Hvis vi initialiserer feltet med en standardværdi, bliver vi også nødt til at omlægge konstruktøren, hvilket gør designet mere stift.

Vi kan bruge en statisk fabriksmetode i stedet:

offentlig statisk bruger createWithDefaultCountry (strengnavn, streng-e-mail) {returner ny bruger (navn, e-mail, "Argentina"); }

Sådan får vi en Bruger forekomst med en standardværdi, der er tildelt Land Mark:

Brugerbruger = User.createWithDefaultCountry ("John", "[e-mailbeskyttet]");

5. Flytning af logik ud af konstruktører

Vores Bruger klasse kunne hurtigt rådne sig ind i et mangelfuldt design, hvis vi beslutter at implementere funktioner, der kræver tilføjelse af yderligere logik til konstruktøren (alarmklokker skal lyde på dette tidspunkt).

Lad os antage, at vi ønsker at give klassen muligheden for at logge det tidspunkt, hvor hver Bruger objekt oprettes.

Hvis vi bare lægger denne logik i konstruktøren, ville vi bryde princippet om et enkelt ansvar. Vi ville ende med en monolitisk konstruktør, der gør meget mere end at initialisere felter.

Vi kan holde vores design rent med en statisk fabriksmetode:

offentlig klasse bruger {privat statisk endelig Logger LOGGER = Logger.getLogger (User.class.getName ()); privat endelig Navn på streng; privat endelig streng-mail; privat endelig String land; // standardkonstruktører / getters offentlig statisk bruger createWithLoggedInstantiationTime (strengnavn, streng-e-mail, strengland) {LOGGER.log (Level.INFO, "Oprettelse af brugerinstans på: {0}", LocalTime.now ()); returner ny bruger (navn, e-mail, land); }} 

Sådan opretter vi vores forbedrede Bruger eksempel:

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

6. Instansstyret instantiering

Som vist ovenfor kan vi indkapsle klumper af logik i statiske fabriksmetoder, inden vi returnerer fuldt initialiseret Bruger genstande. Og vi kan gøre dette uden at forurene konstruktøren med ansvaret for at udføre flere, ikke-relaterede opgaver.

For eksempel, Antag, at vi vil gøre vores Bruger klasse en Singleton. Vi kan opnå dette ved at implementere en instansstyret statisk fabriksmetode:

offentlig klasse bruger {privat statisk flygtig brugerinstans = null; // andre felter / standardkonstruktører / getters offentlig statisk bruger getSingletonInstance (strengnavn, streng-e-mail, strengland) {if (forekomst == null) {synkroniseret (bruger.klasse) {hvis (forekomst == null) {eksempel = ny Bruger (navn, e-mail, land); }}} returner instans; }} 

Gennemførelsen af getSingletonInstance () metode er trådsikker, med en lille præstationsstraff på grund af den synkroniserede blok.

I dette tilfælde brugte vi doven initialisering til at demonstrere implementeringen af ​​en instansstyret statisk fabriksmetode.

Det er dog værd at nævne det den bedste måde at implementere en Singleton på er med en Java enum type, da den både er seriøsikker og trådsikker. For at få de fulde detaljer om, hvordan du implementerer Singletons ved hjælp af forskellige tilgange, se denne artikel.

Som forventet at få en Bruger objekt med denne metode ligner meget de tidligere eksempler:

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

7. Konklusion

I denne artikel undersøgte vi et par anvendelsestilfælde, hvor statiske fabriksmetoder kan være et bedre alternativ til brug af almindelige Java-konstruktører.

Desuden er dette refaktoriseringsmønster så tæt forankret i en typisk arbejdsgang, at de fleste IDE'er vil gøre det for os.

Naturligvis udfører Apache NetBeans, IntelliJ IDEA og Eclipse refactoring på lidt forskellige måder, så sørg først for at kontrollere din IDE-dokumentation.

Som med mange andre refaktoriseringsmønstre, skal vi bruge statiske fabriksmetoder med behørig forsigtighed, og kun når det er værd at afveje mellem at producere mere fleksible og rene designs og omkostningerne ved at skulle implementere yderligere metoder.

Som sædvanligt er alle kodeeksempler vist i denne artikel tilgængelige på GitHub.