(Anty)wzorce projektowe

0

jesli wzorzec jest pojebany to jest antywzorcem - jesli jest nadurzwyany jakis wzorzec to tez ci co to robia nie sa zbyt normalni- singleton to nie anty-wzorzec , to cos co uosabia jakichś mułów static-owych albo kogos kto nie wie jak pisac soft ale upiera sie przy swoich przestazalych porypanych filozofiach, (ok, banuj jak cos moderatorze)

10
jarekr000000 napisał(a):

Grubo, akurat kontenery DI to dla mnie jeden z najgorszych antywzorców. Ale, że już dawno w javie nie piszę to przestałem narzekać.

Świetnie, ale skończmy z tą megalomania. Problemy jakiegoś jednego języka, w którym najwyraźniej nie da się czegoś zaimplementować dobrze, to nie są problemy całego świata. A może to ten język jest antywzorcem?

Kontener DI nie może być antywzorcem, bo nie jest konstrukcją z kodu. To jest narzędzie. Narzędzie można zarówno źle zaimplementować, jak i źle go użyć. (A można też nie robić źle, bo to mocno bez sensu.)
Jeżeli chodzi o antywzorzec (a raczej chyba antypraktykę) związaną z używaniem kontenerów DI, to jest nim wpychanie wszystkich klas do kontenera. Wielu tak robi, nawet robiąc bezsensowne interfejsy z jedną implementacją. Interface-implementation pair to chyba główny antywzorzec obecnych czasów.

A poza tym, to przecież jest cały szereg antywzorców związanych z DI:

  • property injection;
  • ambient context;
  • bastard injection;
  • service locator.

Wzorce są raczej związane z językiem.
Np. w wielu nowych językach singleton jest wbudowany w składnię. Podobnie jak np. lepsze wsparcie do DI (lepsze niż tylko konstruktor).

W językach funkcyjnych jest zwykle dostępny wyższy poziom abstrakcji, więc to co się klepie wg potwarzalnego schematu w javie/ C++/C# jest po prostu dostępne jako funkcja biblioteczna - przestaje być wzorcem.

No nie wiem. To, że jakieś wzorce są wbudowane w język czy biblioteki nie oznacza, że one znikają. Po prostu są łatwiejsze w użyciu.
Za to jeżeli jakieś fragmenty kodu powiela się w celu rozwiązywania pewnych problemów, to są one wzorcami niezależnie od języka. Wzorce to nie jest stała lista 17 konstrukcji opisanych w książce sprzed 30 lat, można tworzyć nowe.

piotrpo napisał(a):

Jeżeli piszesz coś małego, to wpychanie na siłę frameworku z DI, czy nawet jakiejś biblioteki jest bez sensu.

Nie muszę niczego wpychać, w moim świecie mam od razu.

Zresztą we frameworku ktoś to jednak samodzielnie zaimplementował.

Tak, zaimplementował, przetestował i działa, i sprawił, że singletonem można łatwo uczynić dowolny obiekt. To ogromna różnica w porównaniu do wielu pokracznych własnoręcznych implementacji singletona robionych metodą chałupniczą.

Nie znam dobrej książki o wzorcach projektowych.

Ja za taką uznaję Head First Design Patterns, aczkolwiek nie każdemu musi przypaść do gustu.

3

Moim zdaniem największym antywzorcem, wręcz królem antywzorców, jest używanie niepoprawnej nazwy na coś.

Milion razy widziałem kiedy ludzie:

  • Nazywają coś MVC, coś co nie było MVC
  • Nazywanie czegoś OOP, coś co nie jest OOP (czasem zwykły polimorfizm)
  • Nazywanie czegoś abstrakcją, mimo że wcale abstrakcją nie jest
  • Nazywanie czegoś testowaniem, mimo że nic nie testuje
  • Nazywanie czegoś domeną, mimo że nie jest
  • Nazywanie czegoś Continues Integration, mimo że nie jest ani continuous ani Integration
  • Nazywanie czegoś frameworkiem, mimo że nie jest (to chyba 99% caseów)
  • Nazywanie czegoś git flow, mimo że wcale nie jest git flow
  • Nazywanie czegoś modułem lub pakietem mimo że nie jest
  • Nazywanie czegoś CMS'em, mimo że nie jest (to chyba 99.999% caseów)
  • Nazywanie czegoś wzorcem projektowym, mimo że nie jest
  • Nazywanie czegoś co robią TDD, mimo że to obok TDD nie stało
  • Nazywanie czegoś Dependency Injection mimo że nie jest
  • Nazywanie czegoś Scrumem albo Agile, mimo że nie jest
  • Nazywanie czegoś placeholderem mimo że wcale nie holduje place'a.

Z jakiegoś powodu niektórzy lubią te nazwy i lubią je stosować, nawet jesli nie do końca wiedzą czym to coś jest.

PS: noi najlepsze, nazywanie buga featurem :D

4
somekind napisał(a):

Kontener DI nie może być antywzorcem, bo nie jest konstrukcją z kodu. To jest narzędzie. Narzędzie można zarówno źle zaimplementować, jak i źle go użyć. (A można też nie robić źle, bo to mocno bez sensu.)

Wykorzystanie kontenera DI jest konstrukcją z kodu. Narzędziem to jest np. Autofac czy Guice. Nie sądze, żeby w C# te kontenery działały inaczej niż w Javie.
Co więcej większość chyba programistów w Javie uważa, że im te DI pomaga - toczyliśmy tu spory na temat - jestem raczej w małej grupie uważającej, że kontenery DI to takie GOTO naszych czasów:
Czyli łatwo pokazać na krótkim kodzie, że zaoszczędzamy kilka linijek (niezbyt wiele), tylko nie podobają mi się skutki długofalowe (czyli m.in. klasy, które mają tony zależności (bo łatwo wrypać) i tony niepotrzebnych klas w kontenerze - ludzie zapominają, że można tworzyć normalnie obiekty - co zresztą wzmiankujesz).

Faktycznie w Springu (java) dochodzi do tego jeszcze x problemów związanych z runtimowymi aspektami (ale to już wykracza poza czyste DI, to kolejny poziom zrypania).

Co więcej wiem czemu powstały w javie 20 lat temu - przez zrypane frameworki typu JavaEE, servlety, ejb (2.x) . Natomiast nie wiem skąd się kontenery wzięły w C# - czyste kopiowanie na ślepo?

0

To co opisujesz @jarekr000000 to problemy kontenerów bazujących na refleksji. To nie jest tak że nikt tego nie widzi, w Google przerobili kiedyś Daggera tak żeby był statycznym injectorem, czyli generuje kod w locie (chyba przez annoation preprocessing), dzięki temu można zobaczyć jakie faktycznie zależności będą wstrzyknięte (https://dagger.dev/).

Przyznam jednak że magia pozostaje w przypadku gdy jest N wersji tej samej zależności (CachedCredentialProvider, SessionCredentialProvider, HeaderCredentialProvider, ...) i należy wybrać jedną, zwłaszcza jeżeli takich miejsc dodatkowo jest M w kodzie.

Yegor256 od dawna jest zwolennikiem pisania bez kontenerów https://www.yegor256.com/2014/10/03/di-containers-are-evil.html Na małych projektach robię tak jak on, bo co sobie głowę springiem czy guicem zawracać... Na dużych boleśnie doświadczyłem jaki się syf robi w Springu. Myślę że brakuje tutaj czegoś w rodzaju prywatnych zależności to jest żeby móc zrobić nie Configuration classy ale moduły i żeby pewne zależności były dostępne tylko w granicy modułu i nie eksportowane na zewnątrz przez kontener DI. Niestety w Springu wszystko pcha się do jednego wielkiego wora i niestety takie podejście na większych projektach się nie sprawdza.

DI jako zasada dobrego pisania kodu jest OK. DI jako magiczne wire'owanie zależności może przyprawić o ból głowy.

EDIT: Poprawki językowe.

1
0xmarcin napisał(a):

DI jako zasada dobrego pisania kodu jest OK. DI jako magiczne wire'owanie zależności może przyprawić o ból głowy.

Dokładnie, Dependency Injection to nawet nie jest dobry wzorzec - to konieczność :-) Ale korzystanie do DI z jakichkolwiek kontenerów jest dla mnie dziwactwem.
Jestem w stanie sobie wyobrazić projekt, gdzie jest to przydatne: jeśli składamy aplikację z pluginów, których konstelacja nie jest znana w czasie buildu - jest bardzo generyczna i dynamiczna.
Tylko, że to dość nietypowy rodzaj aplikacji.

Z daggerem nie miałem do czynienia, ale nadal nie rozumiem potrzeby. Szczególnie w kotlinie (gdzie daggera się używa), bo akurat kotlin ma dodatkowy cukier usprawniający DI (ale da się bez tego żyć).

0
jarekr000000 napisał(a):

Dependency Injection to nawet nie jest dobry wzorzec - to konieczność :-) Ale korzystanie do DI z jakichkolwiek kontenerów jest dla mnie dziwactwem.

Jak masz klasę A, która potrzebuje mieć w sobie referencję do instancji klasy B to jakoś ta zależność musi być zarejestrowana. Można to zrobić albo przez private Dupa dupa = new Dupa(), albo przez zewnętrzne wstrzyknięcie tej zależności (IoC)

Jestem w stanie sobie wyobrazić projekt, gdzie jest to przydatne: jeśli składamy aplikację z pluginów, których konstelacja nie jest znana w czasie buildu - jest bardzo generyczna i dynamiczna.
Tylko, że to dość nietypowy rodzaj aplikacji.

Nie wiem czy w takim przypadku magia @Autowire da radę. Może być chyba jedynie kolejną warstwą abstrakcji na abstrakcję.

Z daggerem nie miałem do czynienia, ale nadal nie rozumiem potrzeby. Szczególnie w kotlinie (gdzie daggera się używa), bo akurat kotlin ma dodatkowy cukier usprawniający DI (ale da się bez tego żyć).

Dagger jest fajny (moim zdaniem), działa również w Javie. To, że sobie znalazł niszę w Androidzie jest dość zabawne, bo akurat przez zrąbany w założeniach Android Framework i brak dostępu do konstruktorów/fabrykowania komponentów frameworku (np. Activity), nie da się tego użyć dobrze, czyli trzeba wstrzykiwać na polach i trzeba mieć zależność od Daggera w klasie która ma te zależności wstrzykiwane. Swoją drogą, jaki cukier w Kotlinie masz na myśli?

Springowe @Autowire samo z siebie ma moim zdaniem dwie wady:

  • Nie ma sprawdzania compile time
  • Nie pozwala na sensowne dzielenie na podmoduły, czyli jeżeli mamy np. repozytorium, napiszemy na nim @Bean, to będzie dostępne w każdym możliwym miejscu systemu, czyli nie ma najmniejszego problemu z wstrzyknięciem zależności do dowolnego bean'a w dowolnym innym. Nie ma szansy na ten moment refleksji, że skoro trzeba przepychac instancję jakiegoś obiektu przez 5 warstw, to być może coś poszło nie tak.
    Reszta wad tego podejścia, typu:
  • klasy z 20 zależnościami
  • wstrzykiwanie na polach, bo konstruktor ma wtedy mniej parametrów
  • burdel w kodzie, bo "się da"
  • brak ogarnięcia u części dev'ów i niewiedza "jak to działa", "co to robi"

To już raczej kwestia ułańskiej fantazji w wykorzystaniu tego narzędzia.

4

Czyli łatwo pokazać na krótkim kodzie, że zaoszczędzamy kilka linijek (niezbyt wiele), tylko nie podobają mi się skutki długofalowe (czyli m.in. klasy, które mają tony zależności (bo łatwo wrypać) i tony niepotrzebnych klas w kontenerze - ludzie zapominają, że można tworzyć normalnie obiekty - co zresztą wzmiankujesz).

Nie umiejętność korzystania z narzędzia nie powoduje że narzędzie jest do d**y.

Faktycznie w Springu (java) dochodzi do tego jeszcze x problemów związanych z runtimowymi aspektami

Znaczna część konterów dodaje obsługę AOP.

7
piotrpo napisał(a):

Jak masz klasę A, która potrzebuje mieć w sobie referencję do instancji klasy B to jakoś ta zależność musi być zarejestrowana. Można to zrobić albo przez private Dupa dupa = new Dupa(), albo przez zewnętrzne wstrzyknięcie tej zależności (IoC)

Typowy przykład fałszywej alternatywy.

Patrz ani nie ma zależnoći explicite, ani nie ma kontenera. Jest klasyczne DI.

class A {
 private Dupa  dupa;
   A(Dupa dupa) {
      this.dupa = dupa;
   }
}

public static void main()  {
   var dupa = new Dupa("z posypką");
   var A = new A(dupa);
}

(przy okazji: strasznie się w javie pisze :-) )

3

Można robić ręcznie, i pewnie dla małych aplikacji to jest nawet w miarę wygodne. Jednak jak mam projekt z setkami klas to już jest mniej wygodne.

7

@0xmarcin:
Jeśli kod z podlinkowanego artykułu, rejestrujący/tworzący zależności i opisany jako The Right Way miałby mnie zachęcić do zaprzestania korzystania z kontenera DI to dziękuję, nie skorzystam. Wklejam tutaj jako punkt odniesienia:

final Agent agent = new Agent.Iterative(
  new Array<Agent>(
    new Understands(
      this.github,
      new QnSince(
        49092213,
        new QnReferredTo(
          this.github.users().self().login(),
          new QnParametrized(
            new Question.FirstOf(
              new Array<Question>(
                new QnIfContains("config", new QnConfig(profile)),
                new QnIfContains("status", new QnStatus(talk)),
                new QnIfContains("version", new QnVersion()),
                new QnIfContains("hello", new QnHello()),
                new QnIfCollaborator(
                  new QnAlone(
                    talk, locks,
                    new Question.FirstOf(
                      new Array<Question>(
                        new QnIfContains(
                          "merge",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("merge"),
                            new QnMerge()
                          )
                        ),
                        new QnIfContains(
                          "deploy",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("deploy"),
                            new QnDeploy()
                          )
                        ),
                        new QnIfContains(
                          "release",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("release"),
                            new QnRelease()
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    ),
    new StartsRequest(profile),
    new RegistersShell(
      "b1.rultor.com", 22,
      "rultor",
      IOUtils.toString(
        this.getClass().getResourceAsStream("rultor.key"),
        CharEncoding.UTF_8
      )
    ),
    new StartsDaemon(profile),
    new KillsDaemon(TimeUnit.HOURS.toMinutes(2L)),
    new EndsDaemon(),
    new EndsRequest(),
    new Tweets(
      this.github,
      new OAuthTwitter(
        Manifests.read("Rultor-TwitterKey"),
        Manifests.read("Rultor-TwitterSecret"),
        Manifests.read("Rultor-TwitterToken"),
        Manifests.read("Rultor-TwitterTokenSecret")
      )
    ),
    new CommentsTag(this.github),
    new Reports(this.github),
    new RemovesShell(),
    new ArchivesDaemon(
      new ReRegion(
        new Region.Simple(
          Manifests.read("Rultor-S3Key"),
          Manifests.read("Rultor-S3Secret")
        )
      ).bucket(Manifests.read("Rultor-S3Bucket"))
    ),
    new Publishes(profile)
  )
);

Ja rozumiem że w pewnych ekosystemach takich jak Java korzystanie z frameworków/bibliotek DI może być męczące, ale na litość- niech tacy ewangeliści później nie chodzą i nie głoszą wszędzie jakie DI containers to zło ogólnie tylko dla tego że zderzyli się ze złymi przykładami w swoim małym świecie, stosując taką argumentację jako prawda uniwersalna. Zresztą @somekind to wcześniej dobrze podsumował.

Aż mi się przypomina pewien "architekt" kiedy zaczynałem pracować jako programista, który- o zgrozo- zasiał mi na jakiś czas przekonanie jakie to ORM to zło, tylko dla tego że miał z tym niemiłe doświadczenie jakoś w pierwszej dekadzie tego stulecia.

Ja pozostanę jednak przy korzystaniu z kontenerów DI, tak jak w tym przykładzie:

services.AddSingleton<ISomeAbstractFacory, SomeConcreteFactory>()
  .AddScoped<SomeDependency>()
  .AddTransient<IAbstractService, ConcreteService>();

class SomeProcessor
{
  private readonly IAbstractService _service;

  public SomeProcessor(IAbstractService service)
  {
    _service = service;
  }
}
0

@jarekr000000:
Jak rozumiem te pojęcia:
DI - sprawienie, że A będzie miała w sobie referencję do klasy B
IoC - sprawienie, że klasa A nie będzie wiedziała w jaki sposób tworzona jest klasa B, bo jakiś element zewnętrzny zadba o ich utworzenie

Dla mnie oba te terminy nie mają nic wspólnego z kontenerami, bibliotekami, czy frameworkami. To co napisałeś w przykładzie, to IoC, bo klasa Dupa dostaje utworzony na zewnątrz obiekt, czyli jest to IoC (pomijam, że IoC mówi o zależności od interfejsu a nie implementacji.

2
Aventus napisał(a):

@0xmarcin:

Jeśli kod z podlinkowanego artykułu, rejestrujący/tworzący zależności i opisany jako The Right Way miałby mnie zachęcić do zaprzestania korzystania z kontenera DI to dziękuję, nie skorzystam. Wklejam tutaj jako punkt odniesienia:

final Agent agent = new Agent.Iterative(
  new Array<Agent>(
    new Understands(
      this.github,
      new QnSince(
        49092213,
        new QnReferredTo(
          this.github.users().self().login(),
          // Drabinka na 100 nowych instancji

No ale przecież nikt kto dba o jakość swojej aplikacji takiego kodu nie robi :| To by było bez sensu zupełnie, nie dałoby się tego utrzymać. Owszem, takie tworzenie instancji jest konieczne (tzn Java musi stworzyć tyle instancji w takiej hierarchii), ale nie w jednym miejscu i nie w takiej drabince. Te obiekty się komponuje w mniejsze obiekty, tak że w jednym miejscu masz może max 2-3 new Coś().

Mam wrażenie że cały ten wątek ma taką wadę, że jest taka fałszywa dychotomia, że masz:

  • Albo kontener DI
  • Albo drabinkę ifów w main() która zawiera wszystkie instancje każdej klasy

A to jest nieprawda oczywiście.

1
piotrpo napisał(a):

Dagger jest fajny (moim zdaniem), działa również w Javie. To, że sobie znalazł niszę w Androidzie jest dość zabawne, bo akurat przez zrąbany w założeniach Android Framework i brak dostępu do konstruktorów/fabrykowania komponentów frameworku (np. Activity), nie da się tego użyć dobrze, czyli trzeba wstrzykiwać na polach i trzeba mieć zależność od Daggera w klasie która ma te zależności wstrzykiwane. Swoją drogą, jaki cukier w Kotlinie masz na myśli?

DI w kotlinie - moduł ala Guice

open class ServicesModule {
    open val dataSource by lazy { MyDataSource.create() }
    open val myLog by lazy { MyLog() }
    open val myRepo1 by lazy { MyRepo1(dataSource, myLog) }
    open val myRepo2 by lazy { MyRepo2(dataSource, myLog) }
    open val myService1 by lazy { MyService1(myRepo1) }
    open val myService2 by lazy { MyService2(myService1, myRepo2, myLog) }
}

// a to np. podmiana zależności na potrzeby testów.
val testServices = object : ServicesModule {
    override val dataSource = TestDataSource.create()
    override val myService1 by lazy { MockService1(myRepo1) }
}

co tu pomaga: open val czyli wirtualne pole :-)
by lazy czyli opóżniona inicjalizacja.

Btw. kod Yeagora - też uważam za bardzo słaby (te drabinki new).

Kiedyś pisałem jak ja to robię (wtedy w javie):
https://github.com/javaFunAgain/magic_service_story/tree/70_CLEANING
(ta historia i kłótnia już była na 4p)

Generalnie dowcip polega na użyciu hierarchii.

1

@TomRiddle: cytat z podlinkowanego artykułu:

Now, let me show you a real life example of using new to construct an application. This is how we create

Jak dla mnie real life example jest jednoznaczne. I powtarzam- jest to udekorowane nagłówkiem the right way.

2
Aventus napisał(a):

@TomRiddle: cytat z podlinkowanego artykułu:

Now, let me show you a real life example of using new to construct an application. This is how we create

Jak dla mnie real life example jest jednoznaczne. I powtarzam- jest to udekorowane nagłówkiem the right way.

Noo, ale co z tego? Zaprezentowany przykład kodu jest głupi po prostu. To nie prezentuje dobrej struktury kodu. Owszem, prezentuje Dependency Injection, w bardzo przekolorowanej postaci, ale ja bym tak tego kodu nie zostawił.

Stosujesz manipulację, wgl nie wiem czy wiesz.

Bierzesz pomysł, Dependency Injection, wypaczasz ją (Ty, albo autor artykułu) do ogromnych rozmiarów, krytykujesz wynik; i na jego podstawie krytykujesz całą ideę poslugiawania się tylko DI w aplikacji.

0
TomRiddle napisał(a):

Noo, ale co z tego? Zaprezentowany przykład kodu jest głupi po prostu. To nie prezentuje dobrej struktury kodu. Owszem, prezentuje Dependency Injection, w bardzo przekolorowanej postaci, ale ja bym tak tego kodu nie zostawił.

No nic z tego, po prostu odnoszę się do artykułu który został podlinkowany.

Stosujesz manipulację, wgl nie wiem czy wiesz.

Przecież to nie ja wkleiłem link do tego artykułu, a został on tutaj użyty właśnie jako pozytywny przykład alternatywy do bibliotek DI (czy żeby być dokładniejszym- IoC). Więc jak mogę stosować manipulację jeśli odnoszę się do artykułu który ktoś inny tutaj wstawił, i to nie w kontekście przedstawienia tego zamysłu w złym świetle?

Bierzesz pomysł, Dependency Injection, wypaczasz ją (Ty, albo autor artykułu) do ogromnych rozmiarów, krytykujesz wynik; i na jego podstawie krytykujesz całą ideę poslugiawania się tylko DI w aplikacji.

Nie za bardzo rozumiem jak mogę krytykować ideę posługiwania się tylko DI (rozumiem że przez "tylko DI" masz na myśli "bez żadnych bibliotek IoC") skoro ja odnoszę się tylko do artykułu.

Następnie dodałem ogólne dopisek do ludzi którzy- o ironio- robią to o co mnie oskarżasz, tylko że z odwrotnym zamiarem- krytykują korzystanie z bibliotek IoC ogólnie tylko dla tego że zderzyli się z patologią.

Jak ktoś chce sobie tworzyć i wstawiać zależności ręcznie to niech to robi, tylko niech nie głosi wszem i wobec że tak jest lepiej, czy też że robienie tego inaczej to antywzorzec. Bo tak nie jest.

EDIT: I tak, zdaję sobie sprawę że takie ręczne tworzenie zależności można ładnie uporządkować. Chociaż i tak pozostają inne kwestie które kontenery IoC znacznie ułatwiają, np. zarządzanie cyklem życia zależności.

1

Nie wiem czy warto się tu kłócić o kontenery DI. Ten temat był już na 4p wielokrotnie wałkowany :-)

Wrzucając swoje trzy grosze do odpowiedzi @somekind chciałem tylko pokazać, że w kwestii antywzorców nie ma zgody - sa takie, które są ogólnie uznane i są takie, które są bardzo kontrowersyjne.

1
jarekr000000 napisał(a):

Wykorzystanie kontenera DI jest konstrukcją z kodu. Narzędziem to jest np. Autofac czy Guice. Nie sądze, żeby w C# te kontenery działały inaczej niż w Javie.

C# działa inaczej niż Java (np. nie kompiluje słabo typowanego kodu), więc całkiem możliwe, że to wpływa na stabilniejsze działanie albo w ogóle możliwości kontenerów.

Czyli łatwo pokazać na krótkim kodzie, że zaoszczędzamy kilka linijek (niezbyt wiele)

Dla mnie jest spora różnica między kilkoma linijkami na projekt, a kikaset linijek składania obiektów ręcznie. Zwłaszcza, że dodawanie nowych zależności jest bezbolesne, i że nie muszę się martwić sam o cykl życia obiektów.
Przez ileś lat pracy spotkałem się z różnymi problemami tworzonymi przez różne biblioteki i narzędzia, ale źródłem nigdy nie był kontener - no może raz, gdy zrobiono po javowemu i użyto ogromnego pliku XML do zarejestrowania wszystkich klas oddzielnie. Ale takiego czegoś nikt normalny już od dawna nie robi.

tylko nie podobają mi się skutki długofalowe (czyli m.in. klasy, które mają tony zależności (bo łatwo wrypać)

Tak, to prawda, że z kontenerem łatwiej narobić zależności. Tylko nie wiem, czy brak kontenera przed tym nie zabezpiecza. Z tym raczej pomaga Sonar i porządne code review.

Co więcej wiem czemu powstały w javie 20 lat temu - przez zrypane frameworki typu JavaEE, servlety, ejb (2.x) . Natomiast nie wiem skąd się kontenery wzięły w C# - czyste kopiowanie na ślepo?

Wzięły się w celu ułatwienia w tworzeniu aplikacji.
Generalnie łatwiej zintegrować swój kod z kodem jakiegoś frameworka dzięki kontenerowi DI oraz pluginowi łączącemu z tym frameworkiem. Oczywiście, można rypać wszystko samodzielnie, tylko jakby po co? No, chyba, że płacą od linijki.

0

@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()
}

Zakładam, że drugie podejście odrzucamy. W przypadku pierwszego możemy mieć jakieś tam konstrukcyjne wzorce projektowe (factory, proxy itd.)
Możemy też napisać @Autowire, @Inject, czy co tam kontener daje i dalej mamy te wzorce, tylko napisane przez kogoś innego (lepiej, lub gorzej). Zysk z tego taki, że skoro jakiegoś kawałka kodu nie piszemy, to oszczędzimy trochę czasu na pisanie kodu i trochę więcej na usuwanie błędów, oraz sprawi, że kod na którym mam się naprawdę skupić będzie czytelniejszy, będzie też mniejsze ryzyko, że ktoś napisze własne tworzenie jakiejś zależności (wierzę w siłę lenistwa).
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.

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/delivering-hiqh-quality-software-clean-code?utm_source=jvm-bloggers.com&utm_medium=link&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/kotlin-stones/blob/master/stones-server/src/main/kotlin/pl/setblack/kstones/infrastructure/InfrastuctureModule.kt
https://github.com/neeffect/kotlin-stones/blob/master/stones-server/src/main/kotlin/pl/setblack/kstones/stones/StoneModule.kt
https://github.com/neeffect/kotlin-stones/blob/master/stones-server/src/main/kotlin/pl/setblack/kstones/votes/VoteModule.kt

Aplikacja Web= sklejone moduły
https://github.com/neeffect/kotlin-stones/blob/master/stones-server/src/main/kotlin/pl/setblack/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