Static factory method - pattern czy antypattern?

0

Czy static factory method to rzeczywiście jest dobra praktyka? Osobiście całe życie tworzyłem obiekty przez new (o ile spring nie tworzył ich za mnie) ewentualnie korzystałem z buildera i argumenty jakoby static factory method miało być 'a way to encapsulate object creation' średnio do mnie trafia, ale skoro większość ludzi na stackoverflow tak twierdzi to możliwe, źe jednak to ja jestem w błędzie a nie oni. Podsumowując, czy lepiej tworzyć obiekt robiąc

var myDomainObject = new DomainObject("Jan", "Kowalski");

czy raczej

var myDomainObject = DomainObject.create("Jan", "Kowalski");

Co jest najlepszym sposobem? Czy zawołanie new to takie straszne zło? Czy fakt, źe static factory method to metoda statyczna nie sprawia, źe to jednak antypattern? Oczywiście zakładamy, źe kostruktor nie ma żadnej logiki, bo jeźeli by miał to pewnie ma to sens.

3

Tak na prawdę to jeśli masz wybierać pomiędzy new a static-method, to te podejścia są właściwie tożsame. Ja bym wybrał new.

9

Czy fakt, źe static factory method to metoda statyczna nie sprawia, źe to jednak antypattern?

@karellen: durne przeświadczenie. Statyczna metoda to nie antypattern. Cała historia bierze się stąd, że kiedyś było takie przeświadczenie, że wszystko musi być wirtualne. Czy to prawda? Zależy. Na pewno metody prywatne nie są w tej kategorii, tak samo konstruktory.

Co do tematu: jak nazwa nic nie wnosi (jak w tym przypadku) to byłbym za new, bo taka jest tradycja. Jak wnosi np. masz List.createWithElements List.createEmpty List.createWithCapacity to oczywiście taki zabieg pozwala mieć wiele konstruktorów i do tego masz ładnie opisany kod.

Warto dodać, że języki takie jak Go czy Rust nie mają podejścia konstruktora: tam za konstruktor robi "statyczna" metoda. Jest to dużo bardziej eleganckie, bo jak możesz mieć analogiczny ficzer bez dodawania niczego nowego do języka to jest IMO lepiej

1
karellen napisał(a):

Oczywiście zakładamy, źe kostruktor nie ma żadnej logiki, bo jeźeli by miał to pewnie ma to sens.

to jest kluczowe. jeśli konstruktor ma logikę to powinno się ją przenieść do metody fabrykującej. jeśli konstruktor nie ma logiki to nie ma wielkiej różnicy, ale przecież w przyszłości może tę logikę mieć :)

gdybym pisał w języku, który nie robi mi automatycznie metody fabrykującej (kompilator scali robi :) ), to pewnie korzystałbym z new dla zwykłych pojemników na dane, ale dla serwisów robiłbym metody fabrykujące.

metody fabrykujące mają też tę zaletę, że dają większą elastyczność przy założeniu, że chcemy zachowywać wsteczną kompatybilność. to się przydaje w bibliotekach (względnie) szerokiego zastosowania, ale nie ma specjalnie znaczenia w kodzie używanym lokalnie (np. tylko wewnątrz jednego projektu).

slsy napisał(a):

Warto dodać, że języki takie jak Go czy Rust nie mają podejścia konstruktora: tam za konstruktor robi "statyczna" metoda. Jest to dużo bardziej eleganckie, bo jak możesz mieć analogiczny ficzer bez dodawania niczego nowego do języka to jest IMO lepiej

w ruście po prostu nie ma konstruktora. są metody fabrykujące, ale konstruktora nie ma. analogicznie do czystego c, gdzie można tworzyć instancje struktur kodem a'la:

/* Define a variable p of type point, and initialize its first two members in place */
struct point p = { 1, 2 };

i można to opakować w metodę fabrykującą, ale tu nie ma żadnego konstruktora.

6

To jest pattern, statyczna metoda fabryki to nazwane new.

Przykład:

Point2D.fromCartesian(x, y)
Point2D.fromPolar(r, angle)

Ten 2 przykład można jeszcze dopieścić jeżeli język wspiera nazwane parametry:

Point2D.fromPolar(r: r, angle: fi)

Także zapis jest czytelny i jasny.

0
Wibowit napisał(a):
karellen napisał(a):

Oczywiście zakładamy, źe kostruktor nie ma żadnej logiki, bo jeźeli by miał to pewnie ma to sens.

to jest kluczowe. jeśli konstruktor ma logikę to powinno się ją przenieść do metody fabrykującej. jeśli konstruktor nie ma logiki to nie ma wielkiej różnicy, ale przecież w przyszłości może tę logikę mieć :)

gdybym pisał w języku, który nie robi mi automatycznie metody fabrykującej (kompilator scali robi :) ), to pewnie korzystałbym z new dla zwykłych pojemników na dane, ale dla serwisów robiłbym metody fabrykujące.

metody fabrykujące mają też tę zaletę, że dają większą elastyczność przy założeniu, że chcemy zachowywać wsteczną kompatybilność. to się przydaje w bibliotekach (względnie) szerokiego zastosowania, ale nie ma specjalnie znaczenia w kodzie używanym lokalnie (np. tylko wewnątrz jednego projektu).

Moim zdaniem to już jest antypattern, trzymać logikę w takich metodach. Ja przeniósłbym ją wtedy do osobnego obiektu/klasy.

0

@TomRiddle:
no w sumie chwilę po napisaniu mojego poprzedniego posta przypomniałem sobie, że konstrukcję serwisów nierzadko przenoszę do innych klas, np. takich które zajmują się ściśle wstrzykiwaniem zależności / konstruowaniem grafu obiektów (serwisów). często jednak metody fabrykujące trzymam w tym samym pliku co serwis. zależy od indywidualnego osądu :) w każdym razie nie pcham logiki do konstruktorów.

0
Wibowit napisał(a):

@TomRiddle:
no w sumie chwilę po napisaniu mojego poprzedniego posta przypomniałem sobie, że konstrukcję serwisów nierzadko przenoszę do innych klas, np. takich które zajmują się ściśle wstrzykiwaniem zależności / konstruowaniem grafu obiektów (serwisów). często jednak metody fabrykujące trzymam w tym samym pliku co serwis. zależy od indywidualnego osądu :) w każdym razie nie pcham logiki do konstruktorów.

Podobnie mam.

Jak dla mnie metody statyczne nie różnią się od funkcji globalnych (gdyby Java je miała). Staram się ich nie używać.

4

Cytat z Effective Java

Consider static factory methods instead of constructor

One advantage od static factory methods is that, unlike constructors, they have names.
If the parameters to a constructor do not, in and of themselves, describe the object
being returned, a static factory with a well-chosen name is easier to use and the resulting
client code easier to read.

A second advantage of static factory methods is that, unlike constructors,
they are not required to create a new object each time they're invoked.

This allows immutable classes to use preconstructed instances, or to cache
instances as they're constructed, and dispense them repeatedly to avoid creating
unnecessary duplicate objects.

A third advantage of static factory method is that, unlike constructors,
they can return an object of any subtype of their return type.

This gives you great flexibility in choosing the class of the returned object.

A fourth advantage of static factories is that the class of the returned object
can vary from call to call as a function of the input parameters.
Any subtype
of the declared return type is permissible. The class of the returned object
can also vary from release to release.

A fifth advantage of static factories is that the class of the returned object
need not exist when the class containing the method is written.
Such flexible
static factory methods from the basis of service provider framework, like the
Java Database Connectivity API (JDBC).

Od siebie dodam jeszcze, że możesz zwrócić jakiś Either z błędem jeżeli np. nie chcesz rzucać wyjątkami w konstruktorze.

0
lookacode1 napisał(a):

Cytat z Effective Java

[...]

Od siebie dodam jeszcze, że możesz zwrócić jakiś Either z błędem jeżeli np. nie chcesz rzucać wyjątkami w konstruktorze.

Tylko że ten cytat z "Consider static factory methods instead of constructor", tak na prawdę listuje przewagi tworzenia obiektów w metodach w ogóle, a nie koniecznie w static-method.

Jak zrobisz zwykłą metodę fabrykującą (nie statyczną) to wszystkie te zalety wymienione wyżej się nadal spełniają.

Tada! :D

Consider static factory methods instead of constructor

One advantage od static factory methods is that, unlike constructors, they have names.
If the parameters to a constructor do not, in and of themselves, describe the object
being returned, a static factory with a well-chosen name is easier to use and the resulting
client code easier to read.

A second advantage of static factory methods is that, unlike constructors,
they are not required to create a new object each time they're invoked.

This allows immutable classes to use preconstructed instances, or to cache
instances as they're constructed, and dispense them repeatedly to avoid creating
unnecessary duplicate objects.

A third advantage of static factory method is that, unlike constructors,
they can return an object of any subtype of their return type.

This gives you great flexibility in choosing the class of the returned object.

A fourth advantage of static factories is that the class of the returned object
can vary from call to call as a function of the input parameters.
Any subtype
of the declared return type is permissible. The class of the returned object
can also vary from release to release.

A fifth advantage of static factories is that the class of the returned object
need not exist when the class containing the method is written.
Such flexible
static factory methods from the basis of service provider framework, like the
Java Database Connectivity API (JDBC).

0

Znów mówię nieprecyzyjnie, przepraszam. Przez nie używanie polimorfizmu miałem na myśli nie że ja tam robie jakieś dziedziczenie, ale że używam narzędzia stworzonego do tego żeby tworzyć polimorficzny kod. No bo spójrz, koszt jaki musze ponieść żeby utworzyć niestatyczną metodę to: a) dla mnie - utworzenie nowej klasy i nowego konstruktora, b) dla runtime'u używanie metody wirtualne przed dodatkowy wskaźnik zamiast normalnego wskaźnika (niby JVM potrafi to optymalizować jak nie ma dziedziczenia, ale to znów dodatkowy narzut dla optymalizotora)
Dla mnie to używanie dodatkowego narzędzia po to żeby go użyć niezgodnie z przeznaczeniem, a wzasadzie w ogóle nie skorzystać z zalet jakie daje

To jest przykład ze static-method

class TheObject {
  public int field;
  
  public static TheObject create() {
    return new TheObject();
  }
}

class Application {
  public static void main(String[] args) {
    new Application().run();
  }
  
  public void run() {
    TheObject object = TheObject.create();
    object.field = // do stuff
    object.field = // do stuff
  }
}

to jest zwykła factory method:

class TheObject {
  public int field;
}

class Application {
  public static void main(String[] args) {
    new Application().run();
  }

  public void run() {
    TheObject object = this.create();
    object.field = // do stuff
    object.field = // do stuff
  }
  
  public TheObject create() {
    return new TheObject();
  }
}

Gdzie tu potrzebny jakiś polimorfizm?

Moim zdaniem przykład drugi, ze zwykła, niestatyczną metodą to jest way-to-go; a to "static-method factory" to smutny żart, nie wiem po co ktokolwiek miałby z niego korzystać.

1

Czyli metody tworzące chcesz umieszczać w klasie która potrzebuje tego obiektu? A jak będą go potrzebować dwie różne klasy to zrobisz kopiuj wklej?

class TheObject {
  public int field;
}

class Client1 {
  public void doSomething() {
    TheObject object = create();
  }
  
  public TheObject create() {
    return new TheObject();
  }
}

class Client2 {
  public void doSomething() {
    TheObject object = this.create();
  }
  
  public TheObject create() {
    return new TheObject();
  }
}

Czy będziesz chciał wydzielić tą metode fabrykującą do osobnej klasy?

class TheObject {
  public int field;
}

class TheObjectCreator() {
  public TheObject create() {
    return new TheObject();
  }
}

class Client1 {
  public void doSomething() {
    TheObject object = new TheObjectCreator().create();
  }
}

class Client2 {
  public void doSomething() {
    TheObject object = new TheObjectCreator().create();
  }
}

W Scali zrobię po prostu object na to (jeden new mniej)

class TheObject(val field: Int) {
}

object TheObject {
  def create: TheObject = {
    new TheObject()
  }
}

class Client1() {
  def doSomething() = {
    val theObject = TheObject.create
  }
}

class Client2() {
  def doSomething(String[] args) = {
    val theObject = TheObject.create
  }
}
2
KamilAdam napisał(a):

Czyli metody tworzące chcesz umieszczać w klasie która potrzebuje tego obiektu? A jak będą go potrzebować dwie różne klasy to zrobisz kopiuj wklej?

Tak. Zawsze jak chcę użyć logiki w dwóch miejscach to robię kopiuj-wklej :>

No oczywiście że nie, robię to co zawsze - wydzielam wspólną część logiki do mniejszego obiektu/klasy/funkcji, i używam go w obu miejscach. Dokładnie w taki sam sposób, gdybym każdą inną logikę chciał użyć w dwóch miejscach.


Po prostu chcialem zaznaczyć, w rozmowie że pomiędzy stylem z new vs. stylem z factory-method, nie ma żadnych różnic; a wszystkie zalety "static-method" tutaj wymienione są też prawdziwe odnośnie zwykłych metod.

0
TomRiddle napisał(a):

Po prostu chcialem zaznaczyć, w rozmowie że pomiędzy stylem z new vs. stylem z factory-method, nie ma żadnych różnic; a wszystkie zalety "static-method" tutaj wymienione są też prawdziwe odnośnie zwykłych metod.

Dla mnie jedyną wadą jest to nadmiarowe new, ale nie będę się kłócił że to duża wada

5

To troche zabawne, ze Javovcy maja awersje do statycznych metod ale mutowalnym stanem sraja jak wlezie :D :D

2

Będę Gargamelem. Jak ja nie cierpię tych statycznych metod....
Dlaczego?
Kijowo się je testuje. Nie wnoszą nic czego nie dałoby mi utworzenie obiektu i wywołanie z niego metody np. Klasa
potatoFactory a w środku createFries, createPuree.

W pełni popieram @TomRiddle, bo nadal nie widzę żadnej przewagi w używaniu statycznych metod, w kwestii, że tak to ujmę - fabrykowania obiektów.

Często widzę po kolegach co mi projekcie dopisują static, to potem i unit testów jakoś mniej, I kod jakis brzydszy.

4

@TomRiddle: To może inaczej. Weźmy JavaDoca dla klasy List. Są tam metody statyczne of oraz copyOf. Dzieki czemu mogę napisac prosto

var list1 = List.of(1, 2, 3, 5, 7, 9);
var list2 = List.copyOf(list1);

I teraz załóżmy że metody statyczne są złe i wiedzą o tym twórcy biblioteki standardowej Javy i zrobili też wersję bez metod statycznych:

var list1 = new ListCreator().of(1, 2, 3, 5, 7, 9);
var list2 = new ListCreator().copyOf(list1);

Dlaczego to drugie API miało by być lepsze? Nie widze żadnego sensu w tym, ale może jeszcze mało widziałem

0

@KamilAdam: glupio spytam, bo wiesz... Ja tylko w phpie robię... Ale co by było złego w tym?

var listFactory = new ListCreator();
var list1 = listFactory.of(1, 2, 3, 5, 7, 9);
var list2 = listFactory.copyOf(list1);

Przy czym metody of i copy Of nie byłyby statyczne.

3
axelbest napisał(a):

@KamilAdam: glupio spytam, bo wiesz... Ja tylko w phpie robię... Ale co by było złego w tym?

var listFactory = new ListCreator();
var list1 = listFactory.of(1, 2, 3, 5, 7, 9);
var list2 = listFactory.copyOf(list1);

Przy czym metody of i copy Of nie byłyby statyczne.

A w czym to jest lepsze od statica? Bo takie bylo de facto pytanie. To ze akurat bylo uzyte dwa razy to szczegol.

> var listFactory = new ListCreator();
> var list1 = listFactory.of(1, 2, 3, 5, 7, 9);

zostanmy przy tym

0
KamilAdam napisał(a):

@TomRiddle: To może inaczej. Weźmy JavaDoca dla klasy List. Są tam metody statyczne of oraz copyOf. Dzieki czemu mogę napisac prosto

var list1 = List.of(1, 2, 3, 5, 7, 9);
var list2 = List.copyOf(list1);

No dobra, z takimi general-purpose klasami jak jak List to możesz mieć static-method, zgadzam się.

Ale to nie oznacza że cała aplikacja powinna tak wyglądać. Czym innym jest util do tworzenia list, a czym innym jest napisanie logiki biznesowej na statikach.

7
TomRiddle napisał(a):

No dobra, z takimi general-purpose klasami jak jak List to możesz mieć static-method.

Czyli mamy precedens :P Teraz trzeba tylko ustalić które są general-purpose a które nie :D

2

Pytanie jest jeszcze takie, jak często wytwarzamy kod generał purpose? Ja osobiście wcale takiego nie robię. Nie pamiętam też od wielu lat bym takie rzeczy robił. Jedyne co to flashbacki z woj..tfuu pierwszej pracy, gdzie się robilo różne Utils/Common/Tool klasy. Ale to raczej gunvo kod był, a nie programowanie.

0

Ale poprosze przyklad gdzie statyczna metoda pokroju List.of jest do d**y bo klasa nie jest general-purpose, nie zrobisz z niej biblioteki, jest specyficzna dla Twojej domeny/aplikacji.
Serio pytam. Bo ja zawodowo pisze FP. Co prawda nie w Haskellu bo w Scali ale tak dla uzmyslowienia no to przeciez w Haskellu wszystko jest wirtualnie statyczne bo nie ma klas :)

EDIT:
@TomRiddle: powiedzial madrze, ze logika biznesowa nie powinna byc w staticach. Ja sie zgadzam. EDIT: Moze nie tyle logika biznesowa co jakies zaleznosci, ze np. trzeba zrobic UserRepository.get(id).

Ale

var myDomainObject = new DomainObject("Jan", "Kowalski");

To nie wyglada jakby mialo w srodku jakokolwiek logike. EDIT: W sumie bez sensu to stwierdzenie. *Nie wyglada jakby to mialo zaleznosci od jakiegos serwisu.

0
stivens napisał(a):

Ale

var myDomainObject = new DomainObject("Jan", "Kowalski");

To nie wyglada jakby mialo w srodku jakokolwiek logike. EDIT: W sumie bez sensu to stwierdzenie. *Nie wyglada jakby to mialo zaleznosci od jakiegos serwisu.

No, nie wygląda. Można więc się spodziewać że nie ma.

Jeśli natomiast okazałoby się że on pod spodem bierze sobie coś skądś staticiem, to ja bym był zawiedziony. Implicit-dependency. Zło.

2

Jeśli natomiast okazałoby się że on pod spodem bierze sobie coś skądś staticiem

No to takiego przykladu ja juz bym nie bronil

8

Dla mnie factory method ma 2 zalety:

  1. To taki „named” constructor
  2. Może robić różne rzeczy, np. zwrócić instancje podklasy albo z cache (podobnie jak to robi BigDecimal)
11

Czyli w skrócie: nieważne, że można mieć nadającą kontekst biznesowy nazwę, że można zwrócić informację o niepowodzeniu operacji tworzenia, że można zawrzeć nietrywialną logikę.
Ważne, że jest to przeklęte static, o którym różni wannabe programiści naczytali się, że jest zawsze złe, więc nie używają. (No, a że chodziło o stan, a nie o samo słowo kluczowe, to już nie doczytali.)

0
Charles_Ray napisał(a):

Dla mnie factory method ma 2 zalety:

  1. To taki „named” constructor

Tak, to jest prawda.

  1. Może robić różne rzeczy, np. zwrócić instancje podklasy albo z cache (podobnie jak to robi BigDecimal)

Niby tak, ale to też jest prawda o zwykłej factory-method.

somekind napisał(a):

Czyli w skrócie: nieważne, że można mieć nadającą kontekst biznesowy nazwę, że można zwrócić informację o niepowodzeniu operacji tworzenia, że można zawrzeć nietrywialną logikę.
Ważne, że jest to przeklęte static, o którym różni wannabe programiści naczytali się, że jest zawsze złe, więc nie używają. (No, a że chodziło o stan, a nie o samo słowo kluczowe, to już nie doczytali.)

Tylko głupio jest przyjąć dobrą rzecz ze złych powodów.

I tak jak niektóre argumenty za static-factory methodami są prawdziwe (jak np to co napisał @Charles_Ray), tak inne są nie do końca prawdziwe (bo owszem odnoszą się nie tylko do static-factory-methods, ale też do zwykłych factory-methods). Takie nielegalne, gratisowe punkty.

4
TomRiddle napisał(a):

I tak jak niektóre argumenty za static-factory methodami są prawdziwe (jak np to co napisał @Charles_Ray), tak inne są nie do końca prawdziwe (bo owszem odnoszą się nie tylko do static-factory-methods, ale też do zwykłych factory-methods). Takie nielegalne, gratisowe punkty.

Nie, to nie są nielegalne punkty. Dyskusja dotyczy porównania konstruktor vs statyczna metoda fabrykująca, bo z oczywistych względów taka dyskusja ma sens. Niestatycznych metod fabrykujących nie da się zastosować w tym samym kontekście, więc w ogóle nie biorą udziału w tym konkursie.

Problem polega na określeniu, w jaki sposób klasa ma umożliwiać tworzenie jej obiektów innym klasom, to nie jest miejsce dla niestatycznych metod.

0
somekind napisał(a):
TomRiddle napisał(a):

I tak jak niektóre argumenty za static-factory methodami są prawdziwe (jak np to co napisał @Charles_Ray), tak inne są nie do końca prawdziwe (bo owszem odnoszą się nie tylko do static-factory-methods, ale też do zwykłych factory-methods). Takie nielegalne, gratisowe punkty.

Nie, to nie są nielegalne punkty. Dyskusja dotyczy porównania konstruktor vs statyczna metoda fabrykująca, bo z oczywistych względów taka dyskusja ma sens. Niestatycznych metod fabrykujących nie da się zastosować w tym samym kontekście, więc w ogóle nie biorą udziału w tym konkursie.

No to nie powinniśmy też przywoływać argumentów które odnoszą się do elementów które nie biorą udziału w konkursie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1