(Anty)wzorce projektowe

1
piotrpo napisał(a):

@jarekr000000: Ale przecież w klasie, która korzysta z tego modułu nadal będziesz miał albo ręczne utworzenie takiego obiektu, albo dostaniesz to z zewnątrz. Czyli alternatywą jest:

class ActuallyDoingSomething(val serviceModule:ServiceModule)

Czyli musi gdzieś istnieć metoda fabrykująca ActuallyDoingSomething, która będzie mieć wiedzę jak utworzyć ServiceModule, lub pozyskać jego instancję.

lub

class ActuallyDoingSomething{
   val serviceModule = ServiceModule()
}

Dlaczego mamy odrzucać drugie podejście? To właśnie robię.
Raz na aplikację - w main - w końcu się deklaruje jakich zależności używam.

fun main() {

    val serviceModule = ServiceModule()
// to trochę pseudokod - dokładnie takich linijek nie mam - ale mniej więcej 
    runServer(8080, routing {
  serviceModule.myService1.api() + serviceModule.myService2.api()
})
}

Podobnie w testach.

W dodatku to co napisałeś też jest obarczone wadami - masz za zależnością do ServiceModule ukryte ileś tam prawdziwych zależności, czyli to co jest efektem wstrzykiwania na polach, tylko robisz to ręcznie.

nie widzę tych wad - service module to dokładnie to samo co module w Guice albo @Configuration w springu. Ważne, że mogę każdą zależność (nawet głęboką) wygodnie podmienić, w testach, w podmodule - gdziekolwiek. Różnica kluczowa jest taka, że nie muszę się trzymać "płaskiego podejścia" i nie jestem uzależniony od inwencji artystycznej twórców kontenera. Zarówno cykl życia obiektów, jak i sposoby tworzenia zależności mogą być dowolne.

0

Nie rozumiem osób które widzą tą drabinkę od Yegora256 która:
1) Jest sprawdzana na etapie kompilacji kodu
2) Działa razem z nawigacją IDE
3) Jest robiona zawsze per moduł, a moduły same w sobie nie są za duże
i piszą że jest zła i nieutrzymywalna, ale już taki Spring XML na 1k linii czy podobny syf w configuration classes jest OK.

Niestety produkcyjnego kodu nie mogę pokazać, ale w każdej firmie widziałem to samo albo XML > 1k linii albo syf z Configuration classes które na wzajem siebie importują albo ComponentScan który zamula jak jest 4GB jarów do skanowania i może sprawiać inne ciężkie w dbg problemy.

Co ciekawe dzisaj pojawił się dobry wpis na JVM Bloggers: https://unknownexception.eu/d[...]amp;utm_campaign=jvm-bloggers gdzie również poruszany jest temat worek-DI:

Encapsulation also matters in the DI container (like Spring). It is easy to put everything as a bean into a context and autowire things everywhere. Unfortunately, it is not that easy to unwire spaghetti code like this. Put only important components into the DI context, encapsulate everything else.

Czyżby na naszych oczach tworzyła się nowa dobra praktyka?

0

@0xmarcin: Czyżby na naszych oczach tworzyła się nowa dobra praktyka? - ta praktyka jest szerzona przez naszego Czarnego Czarodzieja od jakiegoś już czasu.
Przestał o tym wspominać bo chyba porzucił Jave.
Ja ze zgrozą patrzę jak ludzie bez zastanowienia robią beana do każdej pierdoły typu "NextDayOrderCalculator" a potem wychodzi z tego 20 parametrów konstruktora (bo przecież tak się aktualnie wstrzykuje).

1

Drabinka u Yegora256 jest słaba, bo pomija kluczowy problem - drzewo zależności nie jest statyczne. Ono jest zwykle statyczne dla działającej aplikacji (ale niekoniecznie). Natomiast przydaje się mieć inne zależności co najmniej w testach. Yegor olewa ten problem. Oczywiście problem ma proste rozwiązanie, ale znalezienie tego rozwiązania wymaga od czytelnika umiejętności programowania, a to raczej mocno optymistyczne założenie.

1

@jarekr000000:
Czyli raz na aplikację tworzysz grubaśny obiekt, który zawiera wiedzę o wszystkich zależnościach wykorzystywane w aplikacji. Obiekt z zależnościami przekazujesz do aplikacji i do ~każdego obiektu, który będzie go potrzebować do działania, albo utworzenia jakiegoś obiektu. W skrócie robisz sobie z ręki taki "wszystko mający kontekst" i przekazujesz go w dół ręcznie (przez parametry konstruktorów, lub metod). Jeżeli masz jakąś drabinkę 5 klas, gdzie na koniec okazuje się, że jednak potrzebne jest dodanie jakiejś referencji na repozytorium, service bus, key vault, to dodajesz kontekst do konstruktora i tych 5 klas wyżej, dopisujesz po linijce i już?

3

@piotrpo:
Odwrotnie - mam taki gruby obiekt, który ma wszystkie zależności (a w praktyce fabryki tych zależności).
Z tego obiektu wyciągam odpowiednie serwisy i je "odpalam".

Oczywiście te grube obiekty są podzielone na moduły. Czyli w ramach jakiegoś modułu widzę tylko lokalne zależności + te związane z infrastrukturą - czyli np. "dbConnection".
Ale gdzieś tam jest sobie main i tam moduły sklejone w jeden.

Przykład z absurdalnego projekciku na gh:
Moduły

https://github.com/neeffect/k[...]ucture/InfrastuctureModule.kt
https://github.com/neeffect/k[...]kstones/stones/StoneModule.kt
https://github.com/neeffect/k[...]k/kstones/votes/VoteModule.kt

Aplikacja Web= sklejone moduły
https://github.com/neeffect/k[...]lack/kstones/web/WebModule.kt

Test na bazie inMem:

val testStonesModule = object : WebModule(jwtModule) {
                override val infraModule by lazy {
                    object : InfrastuctureModule(jwtModule) {
                        override val jdbcProvider: JDBCProvider = JDBCProvider(testDb.connection)
                    }
                }
            }
6
0xmarcin napisał(a):

Nie rozumiem osób które widzą tą drabinkę od Yegora256 która:
1) Jest sprawdzana na etapie kompilacji kodu
2) Działa razem z nawigacją IDE
3) Jest robiona zawsze per moduł, a moduły same w sobie nie są za duże
i piszą że jest zła i nieutrzymywalna, ale już taki Spring XML na 1k linii czy podobny syf w configuration classes jest OK.

Ale kto pisze, że XML jest OK?
XML jest gorszy niż drabinka, drabinka jest bardziej czasochłonna od podejścia generycznego. Generyczne podejście zapewniają np. kontenery DI.

2
slsy napisał(a):

Template method: dla mnie ten wzorzec to trochę nie wiem jak wydzielić interfejsy więc zrobię burdel w jednym.
Dziedziczenie: zamiast skupiać się na tym co dany kawałek robi (czyli to na czym polegają abstrakcje w językach OOP jak Java) skupiamy się na tym czymś coś jest, co jest błędne, bo rzeczy mają to do siebie, że mają wiele cech i zastosowań. Przykładowo kotek może być obrazkiem, przytulanką albo jedzeniem dla dinozaura. Po samych nazwach klas nie widać co coś robi.
Używanie klas na siłę: chcę zrobić zwykłą funkcję, ale nie mogę. Muszę nazwać ją FunkcjaXXX, gdzie XXX to handler, service, controller, monitor albo inny bzdet.

Ten wątek stał się jak widzę jechaniem po wzorcach a nie antywzorcach.

Co jest nie tak z template pattern? Może po prostu nie miałeś okazji trafić na właściwy przypadek użycia i dlatego masz takie zdanie. Wbrew pozorom to jest bardzo fajny wzorzec pozwalający na przykład na napisanie swojego frameworka w taki sposób że aplikacja kliencka może się wpiąć w określone miejsca i wykonać swój kod. Teraz rozbudowuję mechanizm komunikacji i też używam template pattern żeby móc zdefiniować bazową logikę wyłącznie w miejscu gdzie musi być wykonana a klient na poszczególnych etapach przetwarzania wiadomości mógł wykonać coś swojego.

Co do dziedziczenia to jest różnie. Na pewno nie jest to taki rak jak przedstawiają niektórzy. Dziedziczenie jest spoko jeśli jest zrobione dobrze (jak wszystko, pytanie tylko jak zdefiniować to czy jeszcze jest dobrze czy już zbyt dobrze). Najgorsze dziedziczenie jakie widziałem to obiekty domenowe dziedziczące po sobie tylko po to, żeby z poziomu klasy bazowej uwspólnić jakiś kod infrastrukturalny.

W całej rozciągłości zgadzam się z @Charles_Ray

Antywzorzec to wzorzec zastosowany w złym kontekście. Być może kontekst się zdeaktualizował, być może nieodpowiedni wzorzec jest wykorzystany do rozwiązania problemu. Neal Ford kiedyś mówił o tym na jakimś key nocie. To nie jest tak, że antywzorce wymyślili głupi ludzie, którzy nie umiom w kąkuter.

2

Neal Ford. Akurat przeczytałem ostatni cytat więc wideo w temacie - nie pamiętam co tam jest :-)

Ale pamiętam to :
https://vimeo.com/131634703 tegoż samego opowiadacza.

Gdzieś w środku jest przykład z command pattern, który pokazuje jak bezsensonwne stają się niektóre wzorce w językach funkcyjnych.
Command, Strategy, (Template Method to akurat chyba nie ma żadnego przełożenia na języki funkcyjne) i wiele innych nie mają po prostu sensu w języku gdzie działa się na funkcjach, bo gdyby iść tokiem myślenia "wzorcowego" to do dodania 3 liczb wykorzystuje się strategię. Nonsens, nikt tak nie myśli - po prostu to co było esencją wzorca jest przypadkiem bardziej ogólnego podejścia higher order function - tak oczywiste, że się o tym nie mówi. Tak jak w javie nikt nie mówi, że korzysta ze wzorca zmienna.

Edit: w sumie nie dopisałem najważniejszego - w momencie, w którym język obiektowy dorabia sie iluś tam konstrukcji funkcyjnych te wzorce stają bezsensowne (javie się to przytrafiło).

0

Ten wątek stał się jak widzę jechaniem po wzorcach a nie antywzorcach.

@var: To co jest wzorcem a antywzorcem jest oczywiście kwestią sporną. W końcu pisanie/czytanie kodu jest typowo ludzką czynnością i nie da się określić co jest dobre a co złe.

Co jest nie tak z template pattern? Może po prostu nie miałeś okazji trafić na właściwy przypadek użycia i dlatego masz takie zdanie. Wbrew pozorom to jest bardzo fajny wzorzec pozwalający na przykład na napisanie swojego frameworka w taki sposób że aplikacja kliencka może się wpiąć w określone miejsca i wykonać swój kod. Teraz rozbudowuję mechanizm komunikacji i też używam template pattern żeby móc zdefiniować bazową logikę wyłącznie w miejscu gdzie musi być wykonana a klient na poszczególnych etapach przetwarzania wiadomości mógł wykonać coś swojego.

Możliwe, że moja niechęć jest spowodowana urazami z przeszłości. Moje rozumienie jest takie, że każda sytuacja, gdzie mógłbym użyć template method kończy się tak, że robię strategię. Zalety jakie widzę praktycznie pokrywają się z głównymi zaletami composition over inheritance o których można poczytać gdziekolwiek w internetach. W obu przypadkach (template method, inheritance) uważam, że zalety są mocno przysłonięte przez możliwe nadużycia i ostatecznie nie warto się w to bawić. Pewnie jak ktoś całe życie tworzył swoje hierarchie dziedziczenia to jest mu ciężko zmienić podejście. Ja mam na odwrót i projektowanie kodu opartego o dziedziczenie jest dla mnie sztuką dla sztuki i tworzeniem szumu, który ostatecznie utrudnia rozumowanie o kodzie.

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