Java >> Java tutorial >  >> Tag >> static

Java Constructors vs Static Factory Methods

1. Oversigt

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

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

I denne tutorial vil vi fremhæve fordele og ulemper ved at bruge statiske fabriksmetoder kontra almindelige gamle Java-konstruktører .

2. Fordele ved statiske fabriksmetoder frem for konstruktører

I et objektorienteret sprog som Java, hvad kunne der være galt med konstruktører? Samlet set ingenting. Alligevel siger den berømte Joshua Block's Effective Java Item 1 klart:

"Overvej statiske fabriksmetoder i stedet for konstruktører"

Selvom dette ikke er en sølvkugle, er her de mest overbevisende grunde til at opretholde denne tilgang:

  1. Konstruktører har ikke meningsfulde navne , så de er altid begrænset til standardnavnekonventionen pålagt af sproget. Statiske fabriksmetoder kan have meningsfulde navne , og formidler derfor eksplicit, hvad de gør
  2. Statiske fabriksmetoder kan returnere den samme type, som implementerer metoden/metoderne, en undertype og også primitiver , så de tilbyder et mere fleksibelt udvalg af returtyper
  3. Statiske fabriksmetoder kan indkapsle al den logik, der kræves for at prækonstruere fuldt initialiserede instanser , 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 , hvor Singleton-mønsteret er 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. strengen Klasse

På grund af den velkendte String interning, er det meget usandsynligt, at vi vil bruge strengen klassekonstruktør for at oprette en ny streng objekt. Alligevel er dette helt lovligt:

String value = new String("Baeldung");

I dette tilfælde vil konstruktøren oprette en ny streng objekt, som er den forventede adfærd.

Alternativt, hvis vi ønsker at oprette en ny streng objekt ved hjælp af en statisk fabriksmetode , kan vi bruge nogle af følgende implementeringer af valueOf() metode:

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

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

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

3.2. 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 empty() , af() , og ofNullable() :

Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Baeldung");
Optional<String> value3 = Optional.ofNullable(null);

3.3. Samlingerne Klasse

Sandsynligvis det mest repræsentative eksempel på statiske fabriksmetoder i JDK er Samlingerne klasse. Dette er en ikke-instantiérbar klasse, der kun implementerer statiske metoder.

Mange af disse er fabriksmetoder, der også returnerer samlinger efter at have anvendt en eller anden form for algoritme på den leverede samling.

Her er nogle typiske eksempler på klassens fabriksmetoder:

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List<Integer> unmodifiableList = Collections.unmodifiableList(originalList);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(originalMap);

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

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

4. Brugerdefinerede statiske fabriksmetoder

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

Lad os se et simpelt eksempel.

Lad os overveje denne naive bruger klasse:

public class User {
    
    private final String name;
    private final String email;
    private final String country;
    
    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }
    
    // 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 alle Brugeren forekomster får en standardværdi for landet felt?

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

Vi kan bruge en statisk fabriksmetode i stedet:

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

Sådan får vi en bruger instans med en standardværdi tildelt landet felt:

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

5. Flytning af logik fra konstruktører

Vores Bruger klasse kunne hurtigt rådne op i et mangelfuldt design, hvis vi beslutter os for at implementere funktioner, der ville kræve tilføjelse af yderligere logik til konstruktøren (alarmklokkerne burde lyde på dette tidspunkt).

Lad os antage, at vi ønsker at give klassen mulighed for at logge det tidspunkt, hvor hver Bruger objekt er oprettet.

Hvis vi bare lægger denne logik ind i konstruktøren, ville vi bryde princippet om 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:

public class User {
    
    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;
    
    // standard constructors / getters
    
    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }
}

Her er, hvordan vi ville skabe vores forbedrede Bruger eksempel:

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

6. Forekomststyret instansiering

Som vist ovenfor kan vi indkapsle bidder af logik i statiske fabriksmetoder, før 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 ønsker at gøre vores Bruger klasse a Singleton. Vi kan opnå dette ved at implementere en instansstyret statisk fabriksmetode:

public class User {
    
    private static volatile User instance = null;
    
    // other fields / standard constructors / getters
    
    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

Implementeringen af ​​getSingletonInstance() metoden er trådsikker med en lille ydeevnestraf på grund af den synkroniserede blokering .

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, at den bedste måde at implementere en Singleton på er med en Java enum type, da den både er serialiseringssikker og trådsikker . For de fulde detaljer om, hvordan man implementerer Singletons ved hjælp af forskellige tilgange, se venligst denne artikel.

Som forventet får du en Bruger objekt med denne metode ligner meget de foregående eksempler:

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

7. Konklusion

I denne artikel har vi undersøgt nogle få anvendelsestilfælde, hvor statiske fabriksmetoder kan være et bedre alternativ til at bruge almindelige Java-konstruktører.

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

Selvfølgelig vil Apache NetBeans, IntelliJ IDEA og Eclipse udføre refactoring på lidt forskellige måder, så sørg for først at tjekke din IDE-dokumentation.

Som med mange andre refactoring-mønstre bør vi bruge statiske fabriksmetoder med 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ædvanlig er alle kodeeksemplerne vist i denne artikel tilgængelige på GitHub.


Java tag