Fabryka statyczna vs fabryka jako singleton

24

W niektórych moich kodach mam statyczną fabrykę podobną do tej:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

Podczas przeglądu kodu zaproponowano, aby był to singleton i został wstrzyknięty. Powinno to wyglądać tak:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Kilka rzeczy do podkreślenia:

  • Obie fabryki są bezpaństwowcami.
  • Jedyną różnicą między metodami są ich zakresy (instancja vs. statyczność). Wdrożenia są takie same.
  • Foo to fasola, która nie ma interfejsu.

Argumenty, które poparłem, aby stać się statyczne to:

  • Klasa jest bezstanowa, dlatego nie trzeba jej tworzyć
  • Wydaje się bardziej naturalne, że można wywołać metodę statyczną niż tworzyć instancję fabryki

Argumenty przemawiające za fabryką jako singletonem były:

  • Dobrze jest wstrzykiwać wszystko
  • Pomimo bezpaństwowości fabryki testowanie jest łatwiejsze dzięki iniekcji (łatwe do wyśmiewania)
  • Należy wyśmiewać go podczas testowania konsumenta

Mam kilka poważnych problemów z podejściem singleton, ponieważ wydaje się sugerować, że żadna metoda nigdy nie powinna być statyczna. Wydaje się również sugerować, że narzędzia takie jak StringUtilspowinny być pakowane i wstrzykiwane, co wydaje się głupie. Wreszcie oznacza to, że w pewnym momencie będę musiał kpić z fabryki, co nie wydaje się właściwe. Nie mogę wymyślić, kiedy będę musiał kpić z fabryki.

Co myśli społeczność? Chociaż nie podoba mi się podejście singleton, nie wydaje mi się, aby miałam przeciwko temu strasznie silny argument.

bstempi
źródło
2
Fabryka jest przez coś używana . Aby przetestować tę rzecz w izolacji, musisz przedstawić jej próbkę swojej fabryki. Jeśli nie wyśmiewasz fabryki, błąd może spowodować nieudany test jednostkowy, kiedy nie powinien.
Mike,
Czy zatem opowiadasz się przeciwko statycznym narzędziom w ogóle, czy tylko statycznym fabrykom?
bstempi,
1
Generalnie opowiadam się przeciwko nim. Na przykład w języku C # klasy DateTimei Filesą niezwykle trudne do przetestowania z dokładnie tych samych powodów. Jeśli masz na przykład klasę, która ustawia Createddatę DateTime.Noww konstruktorze, jak przejść do tworzenia testu jednostkowego z dwoma z tych obiektów, które zostały utworzone w odstępie 5 minut? Co powiesz na lata? Naprawdę nie możesz tego zrobić (bez dużo pracy).
Mike,
2
Czy twoja fabryka singletonów nie powinna mieć privatekonstruktora i getInstance()metody? Przepraszamy, niepoprawny próbnik nitów!
TMN
1
@Mike - uzgodniony - często używałem klasy DateTimeProvider, która w sposób trywialny zwraca DateTime.Now. Następnie możesz zamienić go na próbę, która zwraca wcześniej określony czas w twoich testach. Przekazanie go wszystkim konstruktorom to dodatkowa praca - największy ból głowy występuje wtedy, gdy współpracownicy nie kupują go w 100% i wrzucają wyraźne odniesienia do DateTime. Teraz z lenistwa ... :)
Julia Hayward

Odpowiedzi:

15

Dlaczego oddzielisz swoją fabrykę od typu obiektu, który tworzy?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

Oto, co Joshua Bloch opisuje jako swój przedmiot 1 na stronie 5 swojej książki „Skuteczna Java”. Java jest wystarczająco gadatliwa bez dodawania dodatkowych klas i singletonów i tak dalej. Istnieją przypadki, w których oddzielna klasa fabryczna ma sens, ale jest ich niewiele.

Parafrazując Przedmiot 1 Joshua Blocha, w przeciwieństwie do konstruktorów, statyczne metody fabryczne mogą:

  • Mają nazwy, które mogą opisywać określone rodzaje tworzenia obiektów (Bloch używa jako przykładu probablePrime (int, int, Random))
  • Zwróć istniejący obiekt (think: flyweight)
  • Zwraca podtyp oryginalnej klasy lub interfejs, który implementuje
  • Ogranicz gadatliwość (określając parametry typu - patrz na przykład Bloch)

Niedogodności:

  • Klasy bez publicznego lub chronionego konstruktora nie mogą być podklasowane (ale możesz użyć chronionych konstruktorów i / lub pozycji 16: preferuj kompozycję zamiast dziedziczenia)
  • Statyczne metody fabryczne nie wyróżniają się na tle innych metod statycznych (użyj standardowej nazwy, takiej jak () lub valueOf ())

Naprawdę, powinieneś zanieść książkę Blocha do recenzenta kodu i sprawdzić, czy oboje możecie zgodzić się na styl Blocha.

GlenPeterson
źródło
Też o tym myślałem i myślę, że mi się podoba. Nie pamiętam, dlaczego w ogóle je rozdzieliłem.
bstempi,
Ogólnie rzecz biorąc, zgadzamy się co do stylu Blocha. Ostatecznie zamierzamy przenieść nasze metody fabryczne do klasy, w której są one tworzone. Jedyną różnicą między naszym rozwiązaniem a twoim jest to, że konstruktor pozostanie publiczny, aby ludzie mogli nadal bezpośrednio tworzyć instancję klasy. Dzięki za odpowiedź!
bstempi,
Ale ... to okropne. Najprawdopodobniej chcesz mieć klasę, która implementuje IFoo i ma ją przekazać. Korzystanie z tego wzorca nie jest już możliwe; musisz zakodować IFoo, aby oznaczać Foo, aby uzyskać instancje. Jeśli masz IFooFactory, który zawiera IFoo create (), kod klienta nie musi znać szczegółów implementacji, dla którego konkretnego Foo wybrano jako IFoo.
Wilbert
@Wilbert public static IFoo of(...) { return new Foo(...); } Jaki jest problem? Bloch wymienia satysfakcję z twojej troski jako jedną z zalet tego projektu (drugi punkt powyżej). Nie mogę zrozumieć twojego komentarza.
GlenPeterson
Aby utworzyć instancję, ten projekt oczekuje: Foo.of (...). Jednak istnienie Foo nigdy nie powinny być wystawione tylko IFoo powinny być znane (jak Foo jest jednak impl beton IFoo). Posiadanie statycznej metody fabrycznej na konkretnym implem psuje to i prowadzi do sprzężenia kodu klienta z konkretną implementacją.
Wilbert
5

Oto kilka problemów ze statyką w Javie:

  • metody statyczne nie działają zgodnie z „regułami” OOP. Nie możesz zmusić klasy do wdrożenia określonych metod statycznych (o ile mi wiadomo). Dlatego nie można mieć wielu klas implementujących ten sam zestaw metod statycznych z tymi samymi podpisami. Więc utkniesz z jednym z tych działań. Nie można też tak naprawdę podklasować klasy statycznej. Więc nie ma polimorfizmu itp.

  • ponieważ metody nie są obiektami, metod statycznych nie można przekazywać i nie można ich używać (bez mnóstwa kodowania na płycie głównej), z wyjątkiem kodu, który jest z nimi połączony na stałe - co sprawia, że ​​kod klienta jest wysoce sprzężony. (Szczerze mówiąc, nie jest to wyłącznie wina statyki).

  • pola statyki są dość podobne do globałów


Wspomniałeś:

Mam kilka poważnych problemów z podejściem singleton, ponieważ wydaje się sugerować, że żadna metoda nigdy nie powinna być statyczna. Wydaje się również sugerować, że narzędzia takie jak StringUtils powinny być pakowane i wstrzykiwane, co wydaje się głupie. Wreszcie oznacza to, że w pewnym momencie będę musiał kpić z fabryki, co nie wydaje się właściwe. Nie mogę wymyślić, kiedy będę musiał kpić z fabryki.

Dlatego ciekawi mnie pytanie:

  • jakie według Ciebie są zalety metod statycznych?
  • czy uważasz, że StringUtilsjest poprawnie zaprojektowany i wdrożony?
  • jak myślisz, dlaczego nigdy nie będziesz musiał kpić z fabryki?

źródło
Hej, dzięki za odpowiedź! W kolejności, o którą pytałeś: (1) Zaletami metod statycznych są cukier syntaktyczny i brak niepotrzebnej instancji. Miło jest czytać kod, który ma import statyczny i mówić coś w stylu Foo f = createFoo();, niż mieć nazwaną instancję. Sama instancja nie zapewnia żadnego narzędzia. (2) Tak mi się wydaje. Został zaprojektowany, aby przezwyciężyć fakt, że wiele z tych metod nie istnieje w Stringklasie. Zastąpienie czegoś tak fundamentalnego, jak Stringnie jest opcją, więc utils są na dobrej drodze. (kont.)
bstempi
Są łatwe w użyciu i nie wymagają od Ciebie obchodzenia się z żadnymi przypadkami. Czują się naturalnie. (3) Ponieważ moja fabryka jest bardzo podobna do metod fabrycznych Integer. Są bardzo proste, mają bardzo małą logikę i służą jedynie wygodzie (np. Nie ma innego powodu, by je mieć). Główną częścią mojego argumentu jest to, że nie polegają na żadnym stanie - nie ma powodu, aby je izolować podczas testów. Ludzie nie kpią z StringUtilstestów - dlaczego mieliby kpić z tej prostej fabryki wygody?
bstempi,
3
Nie kpią, StringUtilsponieważ istnieje uzasadniona pewność, że metody tam nie wywołują żadnych skutków ubocznych.
Robert Harvey,
@RobertHarvey Przypuszczam, że tak samo twierdzę o mojej fabryce - nic nie zmienia. Podobnie jak StringUtilszwraca jakiś wynik bez zmiany danych wejściowych, moja fabryka również niczego nie modyfikuje.
bstempi,
@bstempi Chciałem tam znaleźć trochę metodologii sokratejskiej. Chyba jestem do niczego.
0

Myślę, że tworzona aplikacja musi być dość nieskomplikowana, w przeciwnym razie jesteś stosunkowo wcześnie w jej cyklu życia. Jedynym innym scenariuszem, jaki mogę wymyślić, w którym nie mielibyście już problemów ze statystyką, jest to, że udało się ograniczyć inne części kodu, które znają fabrykę do jednego lub dwóch miejsc.

Wyobraź sobie, że Twoja aplikacja ewoluuje, a teraz w niektórych przypadkach musisz stworzyć FooBar(podklasę Foo). Teraz musisz przejrzeć wszystkie miejsca w kodzie, które wiedzą o tobie SomeFactoryi napisać logikę, która patrzy someConditioni wywołuje SomeFactorylub SomeOtherFactory. W rzeczywistości, patrząc na twój przykładowy kod, jest gorzej. Planujesz po prostu dodawać metodę po metodzie do tworzenia różnych Foos, a cały kod klienta musi sprawdzić warunek przed ustaleniem, który Foo należy wykonać. Co oznacza, że ​​wszyscy klienci muszą mieć dogłębną wiedzę o tym, jak działa Twoja Fabryka i wszyscy muszą się zmieniać, ilekroć nowe Foo jest potrzebne (lub usunięte!).

Teraz wyobraź sobie, czy właśnie stworzyłeś coś IFooFactory, co tworzy IFoo. Teraz tworzenie innej podklasy lub nawet zupełnie innej implementacji jest dziecinnie proste - wystarczy wprowadzić odpowiedni IFooFactory wyżej w łańcuchu, a następnie, gdy klient go dostanie, zadzwoni gimmeAFoo()i dostanie odpowiednią foo, ponieważ ma odpowiednią fabrykę .

Być może zastanawiasz się, jak to wygląda w kodzie produkcyjnym. Aby dać wam przykład z bazy kodu, nad którą pracuję, która zaczyna się wyrównywać po nieco ponad roku rozwijania projektów, mam kilka fabryk, które są używane do tworzenia obiektów danych pytań (są one trochę jak modele prezentacji ). Mam QuestionFactoryMapw zasadzie hash do wyszukiwania, której fabryki pytań użyć, kiedy. Aby stworzyć aplikację zadającą różne pytania, umieściłem na mapie różne fabryki.

Pytania mogą być prezentowane w instrukcjach lub ćwiczeniach (które oba wdrażają IContentBlock), więc każdy InstructionsFactorylubExerciseFactory dostanie kopię QuestionFactoryMap. Następnie przekazywane są różne fabryki instrukcji i ćwiczeń, ContentBlockBuilderktóre ponownie zachowują skrót, z którego można korzystać. A potem, gdy XML jest załadowany, ContentBlockBuilder przywołuje Fabrykę Ćwiczeń lub Instrukcji z hasza i prosi o to createContent(), a następnie Fabryka deleguje budowę pytań do wszystkiego, co wyciągnie ze swojej wewnętrznej QuestionFactoryMap w oparciu o jest w każdym węźle XML kontra skrót. Niestety , ta rzecz jest BaseQuestionzamiastIQuestion, ale to tylko pokazuje, że nawet jeśli jesteś ostrożny w projektowaniu, możesz popełniać błędy, które mogą cię prześladować.

Twoje wnętrzności mówią ci, że te stany cię zranią, i na pewno jest w literaturze dużo, aby wspierać jelito. Nigdy nie stracisz, posiadając Zastrzyk Zależności, którego użyjesz tylko do testów jednostkowych (nie znaczy to, że wierzę, że jeśli zbudujesz dobry kod, że nie będziesz go więcej używał), ale statyka może całkowicie zniszczyć twoją zdolność aby szybko i łatwo zmodyfikować kod. Dlaczego więc tam pójść?

Amy Blankenship
źródło
Pomyśl przez chwilę o metodach fabrycznych w Integerklasie - prostych rzeczach, takich jak konwersja z ciągu znaków. Właśnie to Foorobi. Mój Foonie ma być przedłużany (moja wina, że ​​tego nie powiedziałem), więc nie mam twoich problemów polimorficznych. Rozumiem wartość posiadania fabryk polimorficznych i korzystałem z nich w przeszłości ... Po prostu nie sądzę, aby były one odpowiednie tutaj. Czy możesz sobie wyobrazić, że musisz utworzyć instancję fabryki, aby wykonać proste operacje, Integerktóre są obecnie wypierane przez statyczne fabryki?
bstempi
Również twoje założenia dotyczące aplikacji są dość nieprecyzyjne. Aplikacja jest dość zaangażowana. Jest to Foojednak o wiele prostsze niż wiele klas. To tylko proste POJO.
bstempi
Jeśli Foo ma zostać rozszerzone, to właśnie z tego powodu Fabryka jest instancją - podajesz poprawną instancję fabryki, aby dać ci odpowiednią Podklasę zamiast wywoływać if (this) then {makeThisFoo()} else {makeThatFoo()}cały kod. Jedną z rzeczy, które zauważyłem w przypadku osób z chronicznie złą architekturą, jest to, że są jak ludzie cierpiący na przewlekły ból. Nie wiedzą, jak to jest nie odczuwać bólu, więc ból, w którym się znajdują, nie wydaje się taki zły.
Amy Blankenship
Możesz także sprawdzić ten link na temat problemów z metodami statycznymi (nawet tymi matematycznymi!)
Amy Blankenship
Zaktualizowałem pytanie. Ty i jedna inna osoba wspomnieliście o przedłużeniu Foo. Słusznie więc - nigdy nie zadeklarowałem tego jako ostatecznego. Footo fasola, której nie należy przedłużać.
bstempi,