W jaki sposób inny popularny język unikałby konieczności używania wzorca fabrycznego, zarządzając podobną złożonością jak w Javie / Java EE?

23

Wzorzec fabryczny (a przynajmniej użycie FactoryFactory..) to podstawa wielu żartów, jak tutaj .

Oprócz posiadania pełnych i „kreatywnych” nazw, takich jak RequestProcessorFactoryFactory.RequestProcessorFactory , czy jest coś zasadniczo nie tak z wzorcem fabrycznym, jeśli musisz programować w Javie / C ++ i istnieje przypadek użycia dla Abstract_factory_pattern ?

W jaki sposób inny popularny język (na przykład Ruby lub Scala ) unikałby używania go podczas zarządzania podobną złożonością?

Pytam dlatego, że widzę głównie krytykę fabryk wymienionych w kontekście ekosystemu Java / Java EE , ale nigdy nie wyjaśniają, w jaki sposób rozwiązują je inne języki / frameworki.

senseiwu
źródło
4
Problemem nie jest korzystanie z fabryk - to doskonały wzór. Nadużywanie fabryk jest szczególnie rozpowszechnione w przedsiębiorczym świecie Java.
CodesInChaos
Bardzo fajny link do żartu. Bardzo chciałbym zobaczyć funkcjonalną interpretację językową pobierania narzędzi do budowy stojaka na przyprawy. Myślę, że to naprawdę doprowadziłoby to do domu.
Patrick M

Odpowiedzi:

28

Twoje pytanie jest oznaczone literą „Java”, nie jest zaskoczeniem, że pytasz, dlaczego szydzi się wzór Factory: sama Java zawiera ładnie zapakowane nadużycia tego wzoru.

Na przykład spróbuj załadować dokument XML z pliku i uruchom dla niego zapytanie XPath. Potrzebujesz tylko 10 linii kodu, aby skonfigurować Fabryki i Konstruktory:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Zastanawiam się, czy faceci, którzy zaprojektowali ten interfejs API, pracowali kiedyś jako programiści, czy po prostu czytali książki i rozrzucali. Rozumiem, że po prostu nie chcieli sami napisać parsera i zostawili to zadanie innym, ale nadal stanowi to brzydką implementację.

Ponieważ pytasz, jaka jest alternatywa, tutaj jest ładowanie pliku XML w C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Podejrzewam, że nowopowstali deweloperzy Java widzą szaleństwo Factory i myślę, że jest to w porządku - jeśli geniusze, którzy sami zbudowali Javę, tak często ich używali.

Fabryczne i wszelkie inne wzory są narzędziami, z których każde nadaje się do określonego zadania. Jeśli zastosujesz je do zadań, które nie są odpowiednie, na pewno będziesz mieć brzydki kod.

Sten Pietrow
źródło
1
Tylko uwaga, że ​​twoja alternatywa C # używa nowszej wersji LINQ to XML - starsza alternatywa DOM wyglądałaby mniej więcej tak: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Ogólnie rzecz biorąc, LINQ to XML jest znacznie łatwiejszy w użyciu, choć głównie w innych obszarach.) A oto artykuł z bazy wiedzy Znalazłem metodę zalecaną przez MS: support.microsoft.com/kb/308333
Bob
36

Jak często ludzie nie rozumieją, co się dzieje (i obejmuje to wielu śmiechów).
To nie jest wzór fabryczny, który jest tak zły, jak to, że używa go wiele (może nawet większość) ludzi.

I to bez wątpienia wynika ze sposobu uczenia się programowania (i wzorców). Schoolkids (często nazywając siebie „studentami”) otrzymują polecenie „tworzenia X za pomocą wzorca Y” i po kilku iteracjach tej myśli, że jest to sposób na rozwiązanie każdego problemu programistycznego.
Dlatego zaczynają stosować określony wzór, który im się podoba w szkole, przeciw wszystkim i wszystkim, niezależnie od tego, czy jest to właściwe, czy nie.

Dotyczy to również profesorów uniwersyteckich, którzy piszą książki o projektowaniu oprogramowania.
Kulminacją tego był system, który z wyraźnym niezadowoleniem musiałem utrzymywać, który został stworzony przez jednego z nich (mężczyzna miał nawet kilka książek o projektowaniu obiektowym do swojego imienia i stanowisko nauczyciela na wydziale CS dużego uniwersytetu ).
Oparty był na układzie trójwarstwowym, przy czym każdy poziom sam układ trójwarstwowy (trzeba oddzielić ...). Na interfejsie między każdym zestawem warstw, po obu stronach, znajdowała się fabryka produkująca obiekty do przesyłania danych do drugiej warstwy oraz fabryka produkująca obiekt do tłumaczenia otrzymanego obiektu na jeden należący do warstwy odbiorczej.
Dla każdej fabryki istniała abstrakcyjna fabryka (kto wie, fabryka może się zmienić, a wtedy nie chcesz, aby kod wywołujący musiał się zmienić ...).
I cały ten bałagan był oczywiście całkowicie nieudokumentowany.

System miał bazę danych znormalizowaną do 5. normalnej postaci (nie żartuję).

System, który był zasadniczo niewiele więcej niż książką adresową, której można używać do rejestrowania i śledzenia dokumentów przychodzących i wychodzących, miał 100 MB bazy kodów w C ++ i ponad 50 tabel baz danych. Drukowanie 500 listów zajęłoby 72 godziny przy użyciu drukarki podłączonej bezpośrednio do komputera z oprogramowaniem.

Oto dlaczego ludzie szydzą z wzorców, a konkretnie z ludzi skupiających się pojedynczo na jednym konkretnym wzorze.

jwenting
źródło
40
Innym powodem jest to, że niektóre wzorce Gang of Four to po prostu pełne gadżety dla rzeczy, które można zrobić trywialnie w języku z pierwszorzędnymi funkcjami, co czyni je nieuniknionymi anty-wzorami. Wzorce „Strategia”, „Obserwator”, „Fabryka”, „Polecenie” i „Metoda szablonów” to hacki do przekazywania funkcji jako argumentów; „Visitor” to włamanie do wykonania dopasowania wzorca na sumie typu / wariancie / oznaczonym związku. Fakt, że niektórzy robią wiele zamieszania na temat czegoś, co dosłownie jest trywialne w wielu innych językach, prowadzi ludzi do kpienia z C ++, Java i podobnych języków.
Doval
7
Dziękuję za tę anegdotę. Czy możesz również trochę rozwinąć kwestię „jakie są alternatywy?” część pytania, więc my też nie jesteśmy tacy?
Philipp
1
@Doval Czy możesz mi powiedzieć, w jaki sposób zwracasz wartość z wykonanego polecenia lub wywołujesz strategię, kiedy możesz przekazać tylko funkcję? A jeśli mówisz o zamknięciach, pamiętaj, że jest to dokładnie to samo, co tworzenie klasy, z wyjątkiem bardziej wyraźnych.
Euforyczny
2
@Euforia Nie widzę problemu, możesz to rozwinąć? W każdym razie nie są dokładnie tym samym. Równie dobrze można powiedzieć, że obiekt z pojedynczym polem jest dokładnie taki sam jak wskaźnik / odwołanie do zmiennej lub że liczba całkowita jest dokładnie taka sama jak (rzeczywista) wyliczenie. Istnieją różnice w praktyce: porównywanie funkcji nie ma sensu; Nie widziałem jeszcze zwięzłej składni anonimowych klas; a wszystkie funkcje z tymi samymi argumentami i zwracanymi typami mają ten sam typ (w przeciwieństwie do klas / interfejsów o różnych nazwach, które są różnymi typami, nawet jeśli są identyczne)
Doval
2
@Euphoric Correct. Byłoby to uważane za zły styl funkcjonalny, jeśli istnieje sposób na wykonanie pracy bez skutków ubocznych. Prostą alternatywą byłoby użycie sumy typu / tagged union do zwrócenia jednej z N rodzajów wartości. Niezależnie od tego, załóżmy, że nie ma praktycznego sposobu; to nie to samo co klasa. W rzeczywistości jest to interfejs (w sensie OOP.) Nietrudno zobaczyć, czy uważasz, że jakakolwiek para funkcji z tymi samymi podpisami byłaby wymienna, podczas gdy dwie klasy nigdy nie są wymienne, nawet jeśli mają dokładnie taką samą treść.
Doval
17

Fabryki mają wiele zalet, które pozwalają na eleganckie projekty aplikacji w niektórych sytuacjach. Jednym z nich jest to, że możesz ustawić właściwości obiektów, które później chcesz utworzyć w jednym miejscu, tworząc fabrykę, a następnie przekazując tę ​​fabrykę. Ale często tak naprawdę nie musisz tego robić. W takim przypadku użycie fabryki tylko zwiększa złożoność, nie dając ci nic w zamian. Weźmy na przykład tę fabrykę:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Jedną z alternatyw dla wzorca fabrycznego jest bardzo podobny wzorzec konstruktora. Główna różnica polega na tym, że właściwości obiektów utworzonych przez fabrykę są ustawiane podczas inicjalizacji fabryki, podczas gdy program budowniczy jest inicjowany z domyślnym stanem, a wszystkie właściwości są ustawiane później.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Ale kiedy problemem jest nadinżynieria, zastąpienie fabryki konstruktorem prawdopodobnie nie stanowi znacznej poprawy.

Najprostszym zamiennikiem dla każdego wzorca jest oczywiście tworzenie instancji obiektów za pomocą prostego konstruktora z newoperatorem:

Widget widget = new ColoredWidget(COLOR_RED);

Konstruktory mają jednak istotną wadę w większości języków zorientowanych obiektowo: muszą zwracać obiekt dokładnie tej klasy i nie mogą zwracać podtypu.

Jeśli musisz wybrać podtyp w czasie wykonywania, ale nie chcesz do tego tworzyć zupełnie nowej klasy Builder lub Factory, możesz zamiast tego użyć metody factory. Jest to statyczna metoda klasy, która zwraca nowe instancje tej klasy lub jednej z jej podklas. Fabrykę, która nie utrzymuje żadnego stanu wewnętrznego, często można zastąpić taką metodą fabryczną:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Nową funkcją w Javie 8 są odwołania do metod, które umożliwiają przekazywanie metod, tak jak w przypadku fabryki bezstanowej. Dogodnie wszystko, co akceptuje odwołanie do metody, akceptuje również każdy obiekt, który implementuje ten sam interfejs funkcjonalny, którym może być również pełnoprawna Fabryka ze stanem wewnętrznym, dzięki czemu można łatwo wprowadzić fabryki później, gdy zobaczy się powód.

Philipp
źródło
2
Fabrykę często można również skonfigurować po utworzeniu. Główną różnicą, afaik, w stosunku do konstruktora jest to, że konstruktor jest zwykle używany do tworzenia pojedynczej, złożonej instancji, podczas gdy fabryki służą do tworzenia wielu podobnych instancji.
Głowonogi
1
dzięki (+1), ale odpowiedź nie wyjaśnia, w jaki sposób inne PL mogłyby to rozwiązać, niemniej dziękuję za wskazanie referencji metod Java 8.
senseiwu
1
Często myślałem, że w kontekście zorientowanym obiektowo sensowne byłoby ograniczenie rzeczywistej konstrukcji obiektu do danego typu i byłoby foo = new Bar(23);to równoważne foo = Bar._createInstance(23);. Obiekt powinien mieć możliwość wiarygodnego zapytania własnego typu przy użyciu getRealType()metody prywatnej , ale obiekty powinny mieć możliwość wyznaczenia nadtypu, który ma zostać zwrócony przez wywołania zewnętrzne do getType(). Kod zewnętrzny nie powinien dbać o to, czy wywołanie new String("A")faktycznie zwraca instancję String[w przeciwieństwie do np. Instancji SingleCharacterString].
supercat
1
Używam wielu fabryk metod statycznych, gdy muszę wybrać podklasę na podstawie kontekstu, ale różnice powinny być nieprzejrzyste dla osoby wywołującej. Na przykład mam szereg klas obrazów, które wszystkie zawierają, draw(OutputStream out)ale generują nieco inny HTML, fabryka metod statycznych tworzy odpowiednią klasę dla danej sytuacji, a następnie wywołujący może po prostu użyć metody rysowania.
Michael Shopsin
1
@ superupat możesz zainteresować się spojrzeniem na cel-c. Tam konstrukcja obiektów składa się zwykle z prostych wywołań metod [[SomeClass alloc] init]. Możliwe jest zwrócenie instancji zupełnie innej klasy lub innego obiektu (takiego jak wartość z pamięci podręcznej)
axelarge 18.04.2014
3

Fabryki tego lub innego rodzaju znajdują się w prawie każdym języku obiektowym w odpowiednich okolicznościach. Czasami po prostu potrzebujesz sposobu, aby wybrać rodzaj obiektu do utworzenia na podstawie prostego parametru, takiego jak ciąg.

Niektórzy ludzie posuwają się za daleko i próbują zaprojektować swój kod, aby nigdy nie trzeba było wywoływać konstruktora, z wyjątkiem wewnątrz fabryki. Rzeczy zaczynają być absurdalne, gdy masz fabryki.

Uderzyło mnie, gdy nauczyłem się Scali i nie znam Ruby, ale wierzę, że jest tak samo, ponieważ język jest na tyle wyrazisty, że programiści nie zawsze próbują przesunąć pracę „hydrauliczną” do zewnętrznych plików konfiguracyjnych . Zamiast skłaniać się do tworzenia fabryk fabrycznych, aby połączyć swoje klasy w różne konfiguracje, w Scali używasz obiektów z pomieszanymi cechami. Stosunkowo proste jest również tworzenie prostych języków DSL w języku do zadań, które są często zbyt zaawansowane w Javie.

Ponadto, jak zauważyły ​​inne komentarze i odpowiedzi, zamknięcia i funkcje pierwszej klasy eliminują potrzebę wielu wzorów. Z tego powodu uważam, że wiele anty-wzorów zacznie zanikać, gdy C ++ 11 i Java 8 zostaną powszechnie przyjęte.

Karl Bielefeldt
źródło
dzięki (+1). Twoja odpowiedź wyjaśnia niektóre moje wątpliwości dotyczące tego, w jaki sposób Scala mogłaby tego uniknąć. Szczerze mówiąc, nie sądzisz, że kiedyś (jeśli) Scala stanie się co najmniej w połowie tak popularna jak Java na poziomie przedsiębiorstwa, pojawią się armie frameworków, wzorców, płatnych serwerów aplikacji itp.?
senseiwu
Myślę, że jeśli Scala przejmie Javę, stanie się tak, ponieważ ludzie wolą „sposób Scali” oprogramowania do projektowania. Bardziej prawdopodobne wydaje mi się to, że Java nadal przyjmuje więcej funkcji, które skłaniają ludzi do przejścia na Scalę, takich jak generyczne Java 5 oraz lambdy i strumienie Java 8, co, miejmy nadzieję, ostatecznie spowoduje, że programiści porzucą anty-wzorce, które pojawią się, gdy te funkcje nie były dostępne. Zawsze znajdą się zwolennicy FactoryFactory, bez względu na to, jak dobre są języki. Oczywiście niektórzy ludzie lubią te architektury, bo inaczej nie byłyby tak powszechne.
Karl Bielefeldt
2

Ogólnie rzecz biorąc, istnieje tendencja, że ​​programy Java są strasznie przeprojektowane [potrzebne źródło]. Posiadanie wielu fabryk jest jednym z najczęstszych objawów nadmiernej inżynierii; dlatego ludzie się z nich śmieją.

W szczególności problem Javy związany z fabrykami polega na tym, że w Javie a) konstruktory nie są funkcjami i b) funkcje nie są pierwszorzędnymi obywatelami.

Wyobraź sobie, że możesz napisać coś takiego (niech Functionbędzie dobrze znanym interfejsem środowiska JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Zobacz, brak interfejsów lub klas fabrycznych. Wiele dynamicznych języków ma pierwszorzędne funkcje. Niestety, nie jest to możliwe w Javie 7 lub wcześniejszej (wdrożenie tego samego w fabrykach pozostawia się czytelnikowi).

Alternatywą jest więc nie tyle „użycie innego wzorca”, ale więcej „użycie języka z lepszym modelem obiektowym” lub „nie nadużywanie prostych problemów”.

Głowonóg
źródło
2
to nawet nie próbuje odpowiedzieć na zadane pytanie: „jakie są alternatywy?”
komar
1
Przeczytaj drugi akapit, a przejdziesz do części „Jak to robią inne języki”. (PS: druga odpowiedź również nie pokazuje alternatyw)
Głowonóg
4
@gnat Czy nie mówi on pośrednio, że alternatywą jest używanie najwyższej klasy funkcji? Jeśli potrzebujesz czarnej skrzynki, która może tworzyć obiekty, twoje opcje to fabryka lub funkcja, a fabryka to tylko ukryta funkcja.
Doval
3
„W wielu dynamicznych językach” jest nieco mylące, ponieważ tak naprawdę potrzebny jest język z pierwszorzędnymi funkcjami. „Dynamiczny” jest do tego ortogonalny.
Andres F.
To prawda, ale jest to właściwość często spotykana w dynamicznych językach. Na początku mojej odpowiedzi wspomniałem o potrzebie pierwszorzędnej funkcji.
Głowonóg