Konstruktory a metody fabryczne [zamknięte]


181

Jaki jest preferowany sposób inicjowania klas podczas modelowania klas:

  1. Konstruktory lub
  2. Metody fabryczne

A jakie byłyby względy korzystania z jednego z nich?

W niektórych sytuacjach wolę metodę fabryczną, która zwraca null, jeśli obiektu nie można zbudować. To czyni kod porządnym. Mogę po prostu sprawdzić, czy zwrócona wartość nie jest pusta przed podjęciem alternatywnej akcji, w przeciwieństwie do zgłoszenia wyjątku od konstruktora. (Osobiście nie lubię wyjątków)

Powiedzmy, że mam konstruktor klasy, który oczekuje wartości id. Konstruktor używa tej wartości do wypełnienia klasy z bazy danych. W przypadku, gdy rekord o podanym identyfikatorze nie istnieje, konstruktor zgłasza wyjątek RecordNotFoundException. W takim przypadku będę musiał zawrzeć konstrukcję wszystkich takich klas w bloku try..catch.

W przeciwieństwie do tego mogę zastosować statyczną metodę fabryczną na tych klasach, które zwrócą null, jeśli rekord nie zostanie znaleziony.

Które podejście jest lepsze w tym przypadku, w przypadku metody konstruktora lub fabryki?

Odpowiedzi:


66

Od strony 108 Wzorów projektowych: elementy oprogramowania obiektowego wielokrotnego użytku Gamma, Helm, Johnson i Vlissides.

Kiedy używasz wzorca metody fabrycznej

  • klasa nie może przewidzieć klasy obiektów, które musi stworzyć
  • klasa chce, aby jej podklasy określały obiekty, które tworzy
  • klasy przekazują odpowiedzialność do jednej z kilku podklas pomocniczych, a Ty chcesz zlokalizować wiedzę, której podklasy pomocniczej jest delegowany

21
Statyczna metoda fabryczna różni się od wzorca projektowego GoF - Factory Method Pattern. stackoverflow.com/questions/929021/...
Sree Rama

Proszę nie porównywać z wzorcem metody Factory wzorców projektowych GoF.
Sree Rama,

137
to mi nic nie wyjaśnia
Sushant

@Sushant, dlaczego tak jest?
PaulD

2
Ta odpowiedź nie odpowiada na pytanie, po prostu przekazuje informacje do przeczytania, aby zrozumieć / wyjaśnić pojęcie ... To jest bardziej komentarz.
Crt,

202

Zapytaj siebie, czym one są i dlaczego je mamy. Oba są po to, aby utworzyć instancję obiektu.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Jak dotąd bez różnicy. Teraz wyobraźmy sobie, że mamy różne typy szkół i chcemy przejść z używania ElementarySchool na HighSchool (która pochodzi z ElementarySchool lub implementuje ten sam interfejs ISchool co ElementarySchool). Zmiana kodu wyglądałaby następująco:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

W przypadku interfejsu mielibyśmy:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Teraz, jeśli masz ten kod w wielu miejscach, możesz zobaczyć, że użycie metody fabrycznej może być dość tanie, ponieważ po zmianie metody fabrycznej jest to gotowe (jeśli użyjemy drugiego przykładu z interfejsami).

I to jest główna różnica i przewaga. Gdy zaczniesz zajmować się złożonymi hierarchiami klas i chcesz dynamicznie utworzyć instancję klasy z takiej hierarchii, otrzymasz następujący kod. Metody fabryczne mogą następnie przyjąć parametr, który mówi metodzie, jaką konkretną instancję należy utworzyć. Załóżmy, że masz klasę MyStudent i musisz utworzyć instancję odpowiedniego obiektu ISchool, aby Twój uczeń był członkiem tej szkoły.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Teraz masz w aplikacji jedno miejsce, które zawiera logikę biznesową, która określa, który obiekt ISchool ma zostać utworzony dla różnych obiektów IStudent.

Tak więc - dla prostych klas (obiektów wartości itp.) Konstruktor jest w porządku (nie chcesz przeceniać swojej aplikacji), ale w przypadku złożonych hierarchii klas preferowaną metodą jest metoda fabryczna.

W ten sposób postępujesz zgodnie z pierwszą zasadą projektowania od grupy czterech książek „Program do interfejsu, a nie implementacja”.


2
Nawet jeśli uważasz, że jest to prosta klasa, istnieje szansa, że ​​ktoś będzie musiał rozszerzyć twoją prostą klasę, więc metoda fabryczna jest jeszcze lepsza. Na przykład możesz zacząć od ElementarySchool, ale później ktoś (w tym ty) może rozszerzyć go o PrivateElementarySchool i PublicElementarySchool.
jack

10
to powinna być zaakceptowana odpowiedź
am05mhz

2
@David, dobra odpowiedź, ale czy możesz rozwinąć na przykładzie, w którym każda implementacja interfejsu może wymagać różnych parametrów do budowy. Oto głupi przykład: IFood sandwich = new Sandwich(Cheese chz, Meat meat);i W IFood soup = new Soup(Broth broth, Vegetable veg);jaki sposób fabryka i / lub konstruktor mogą tutaj pomóc?
Brian

1
Właśnie przeczytałem trzy inne objaśnienia dotyczące celu zastosowania w fabryce i właśnie to dla mnie ostatecznie „kliknęło”. Dziękuję Ci!
Daniel Peirano

Dlaczego to nie jest akceptowana odpowiedź?
Tomas

74

Musisz przeczytać (jeśli masz dostęp) Efektywna Java 2 pozycja 1: Rozważ statyczne metody fabryczne zamiast konstruktorów .

Zalety statycznych metod fabrycznych:

  1. Mają imiona.
  2. Nie są wymagane do tworzenia nowego obiektu przy każdym wywołaniu.
  3. Mogą zwrócić obiekt dowolnego podtypu swojego typu zwrotu.
  4. Ograniczają gadatliwość tworzenia instancji sparametryzowanych.

Wady statycznych metod fabrycznych:

  1. Udostępniając tylko statyczne metody fabryczne, klasy bez konstruktorów publicznych lub chronionych nie mogą być podklasowane.
  2. Nie można ich łatwo odróżnić od innych metod statycznych

4
Wydaje mi się to poważnym błędem w Javie, a następnie ogólnym problemem OOD. Istnieje wiele języków OO, które nawet nie mają konstruktorów, ale podklasowanie działa dobrze.
Jörg W Mittag

1
@cherouvim, dlaczego większość kodu jest pisana przy użyciu konstruktorów, jeśli( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

Słuszne uwagi. Jest jednak specyficzny dla Java. Można przedstawić przypadek funkcji językowej, która odróżnia metody fabryczne od innych metod statycznych.
OCDev,

30

Domyślnie preferowane są konstruktory, ponieważ są łatwiejsze do zrozumienia i napisania. Jeśli jednak chcesz oddzielić subtelności konstrukcyjne obiektu od jego semantycznego znaczenia w rozumieniu kodu klienta, lepiej skorzystaj z fabryk.

Różnica między konstruktorami a fabrykami jest analogiczna do, powiedzmy, zmiennej i wskaźnika do zmiennej. Istnieje inny poziom pośredni, co jest wadą; ale jest też inny poziom elastyczności, co jest zaletą. Dokonując wyboru, dobrze byłoby dokonać analizy kosztów w porównaniu do korzyści.


17
Tak więc (styl TDD) zaczynasz od konstruktorów jako najprostszego sposobu wykonania zadania. A potem refaktoryzuj fabryki, gdy zaczniesz wyczuwać zapach kodu (jak powtarzana logika warunkowa określająca, który konstruktor ma wywołać)?
AndyM,

1
Bardzo ważny punkt. Badanie użytkowników porównujące fabryki i konstruktorów wykazało bardzo znaczące wyniki wskazujące, że fabryki są szkodliwe dla użyteczności API: „użytkownicy potrzebują znacznie więcej czasu (p = 0,005) na budowę obiektu z fabryką niż z konstruktorem” [Wzorzec fabryczny w API Design : Ocena użyteczności ].
mdeff

12

Używaj fabryki tylko wtedy, gdy potrzebujesz dodatkowej kontroli przy tworzeniu obiektów, w sposób niemożliwy do osiągnięcia w przypadku konstruktorów.

Fabryki mają na przykład możliwość buforowania.

Innym sposobem korzystania z fabryk jest scenariusz, w którym nie znasz typu, który chcesz zbudować. Często ten typ użycia występuje w scenariuszach fabryk wtyczek, w których każda wtyczka musi pochodzić z klasy podstawowej lub implementować jakiś interfejs. Fabryka tworzy instancje klas, które wywodzą się z klasy podstawowej lub implementują interfejs.


11

Cytat z „Effective Java”, wyd. 2, poz. 1: Rozważ statyczne metody fabryczne zamiast konstruktorów, str. 5:

„Zauważ, że statyczna metoda fabryczna nie jest taka sama jak wzorzec metody fabrycznej z wzorców projektowych [Gamma95, s. 107]. Statyczna metoda fabryczna opisana w tym punkcie nie ma bezpośredniego odpowiednika we wzorach projektowych”.


10

Oprócz „skutecznej javy” (jak wspomniano w innej odpowiedzi) inna klasyczna książka sugeruje również:

Preferuj statyczne metody fabryczne (z nazwami opisującymi argumenty) od przeciążonych konstruktorów.

Na przykład. nie pisz

Complex complex = new Complex(23.0);

ale zamiast tego pisz

Complex complex = Complex.fromRealNumber(23.0);

Książka posuwa się tak daleko, że sugeruje uczynienie Complex(float)konstruktora prywatnym, aby zmusić użytkownika do wywołania metody fabryki statycznej.


2
Przeczytałem tę część książki, która mnie tu przywiodła
Purple Haze

1
@Bayrem: ja też, czytałem go ostatnio i pomyślałem, że powinienem dodać go do odpowiedzi.
blue_note

1
Na powiązana uwaga, może się okazać pomocne pewne konwencje nazewnictwa wypracowane przez java.time ram dotyczących nazewnictwa from…, to…, parse…, with…, i tak dalej. Należy pamiętać, że klasy java.time są zbudowane tak, aby były niezmienne, ale niektóre z tych konwencji nazewnictwa mogą być przydatne również w przypadku klas zmiennych.
Basil Bourque,

7

Konkretny przykład z aplikacji CAD / CAM.

Ścieżkę cięcia wykonano by przy użyciu konstruktora. Jest to szereg linii i łuków określających ścieżkę do wycięcia. Chociaż seria linii i łuków może być różna i mieć różne współrzędne, łatwo ją obsłużyć, przekazując listę do konstruktora.

Kształt zostałby wykonany przy użyciu fabryki. Ponieważ podczas gdy istnieje klasa kształtu, każdy kształt będzie konfigurowany inaczej w zależności od typu kształtu. Nie wiemy, jaki kształt będziemy inicjalizować, dopóki użytkownik nie dokona wyboru.


5

Powiedzmy, że mam konstruktor klasy, który oczekuje wartości id. Konstruktor używa tej wartości do wypełnienia klasy z bazy danych.

Ten proces zdecydowanie powinien znajdować się poza konstruktorem.

  1. Konstruktor nie powinien uzyskiwać dostępu do bazy danych.

  2. Zadaniem i powodem dla konstruktora jest inicjalizacja elementów danych i ustanowienie niezmiennika klasy za pomocą wartości przekazanych do konstruktora.

  3. W przypadku wszystkiego innego lepszym podejściem jest zastosowanie statycznej metody fabrycznej lub w bardziej złożonych przypadkach oddzielnej klasy fabryki lub producenta .

Niektóre linie przewodnika konstruktora od Microsoft :

Wykonuj minimalną pracę w konstruktorze. Konstruktory nie powinny wykonywać dużo pracy poza przechwytywaniem parametrów konstruktora. Koszt jakiegokolwiek innego przetwarzania należy opóźnić do momentu, gdy będzie wymagany.

I

Rozważ zastosowanie statycznej metody fabrycznej zamiast konstruktora, jeśli semantyka żądanej operacji nie jest odwzorowana bezpośrednio na konstrukcję nowej instancji.


2

Czasami musisz sprawdzić / obliczyć niektóre wartości / warunki podczas tworzenia obiektu. A jeśli może rzucić wyjątek - konstrukcja jest bardzo zła. Musisz więc zrobić coś takiego:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

Gdzie wszystkie dodatkowe obliczenia są w init (). Ale tylko Ty jako programista naprawdę wiesz o tej init (). I oczywiście po miesiącach po prostu o tym zapominasz. Ale jeśli masz fabrykę - po prostu zrób wszystko, czego potrzebujesz w jednej metodzie, ukrywając tę ​​init () przed bezpośrednim wywołaniem - więc nie ma problemów. Dzięki takiemu podejściu nie ma problemów z upadkiem na tworzenie i wyciek pamięci.

Ktoś powiedział ci o buforowaniu. To jest dobre. Ale musisz również pamiętać o wzorze Flyweight, który jest przyjemny w użyciu w stylu Factory.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.