Kiedy stosować final

0

Zastanawiam się czy nie nadużywam final i zastanawia mnie Wasz pogląd na to.

Po 1, relacje typu service i repository czy jakakolwiek inna zależność, która sprawia że klasa A nie może działać bez klasy B, wtedy zawsze final się przydaje

public class SomeService {
    private final SomeRepository someRepository;
    
    public SomeService(SomeRepository someRepository) {
        this.someRepository = someRepository;
    }
}

Po 2, niemutowalne struktury danych, o ile tylko jest to możliwe i nie przeszkadza JPA czy cokolwiek innego

public class SomeDataClass {
    private final String data;

    public SomeDataClass(String data) {
        this.data = data;
    }
}

Po 3, zmienne w metodach

    public String search(String value) {
        final var searchResult = someRepository.find(value);
        return searchResult != null ? processResult(searchResult) : handleNoResult();
    }

Po 4, parametry metod

    public String search(final String value) { ... }

O ile pierwsze dwa przypadki jeszcze są dość często spotykane, to zauważyłem że w 3 i 4 jestem dość odosobniony przynajmniej tam gdzie pracuje. Jak to wygląda u Was? Dajecie final wszędzie bez zastanowienia, całkowicie tego unikacie, czy może zależy od sytuacji (jeżeli tak, to również kiedy stosujecie a kiedy nie)

Edit#

w sumie jest jeszcze 5 przypadek - final String method() i 6 - final class SomeClass { }. Osobiście chyba tego jeszcze nie użyłem w komercyjnym kodzie, ale również interesuje mnie Wasz pogląd na to

15

Kiedy stosować final ?

Zawsze. (Ja piszę tak jak podałeś w 1. 2. 3. i 4.)

Ihmo w Javie zrobiono błąd projektowy. W Scali jest trochę lepiej, ale dopierow Ruscie jest dobrze. Zmienne powinny być domyślnie finalne i dopiero po dodaniu słówka mut powinny stawaś się niefinalne

1

Pracowałem w projekcie, gdzie Eclipse był tak skonfigurowany, że przy "save" wstawiał "final" gdzie się da. Nie pamiętam problemów z tym.

1

Przy zmiennych i parametrach nie ma totalnie żadnego sens, zwłaszcza że optymalizator i tak je doda przy kompilacji gdzie się da.

Myślę że ideą większści ludzi było to, że final jest kolorowane jako słowo kluczowe i stanowi pewien wyznacznik deklaracji zmiennej, łatwiej go dostrzec. Zauważ że ludzie którzy piszą final przy zmiennych, jak przejdą na javę 11, to zaczynają używać var (też słowo kluczowe), mimo że jest to pewne przeciewieństwo, jedyną wspólną rzeczą jest że var to też słowo kluczowe, które IDE podświetla i też jest wyznacznikiem deklaracji.

Słówko wyjaśnienia - myślę że nie ma to żadnego sensu, ponieważ pracując lata w firmach w Javie, zauważyłem że final w żaden sposób nie "broni" przed przypadkowym przypisaniem, bo nawet jeśli ktoś przypisze drugi raz to nie zastanowi się "hmm, should this be final"? Tylko od razu uzna że tak i po prostu wymaże final z deklaracji.

final przy polach pisz zawsze kiedy przypisujesz zależności, które powinny być ustawione w konstruktorze (np. SomeRepository). Wyjątkiem są zależności dostarczane np przez springa i @Autowire, wtedy się nie da.

Kiedy masz obiekt stanowy no to siła rzeczy nie zrobisz żeby był immutable.

4

Klasy powinny być finał by default o ile nie są zaprojektowane na dziedziczenie, tak to jest w Kotlinie.

0
scibi92 napisał(a):

Klasy powinny być finał by default o ile nie są zaprojektowane na dziedziczenie, tak to jest w Kotlinie.

Mały problem. Sensowni programiści i tak nie używają dziedziczenia, i jedyne dla jakich to ma sens to abstract class. + Weź pod uwagę że wtedy ciężej byłoby pisać np framework do moków, albo robić anonmiowe klasy.

5

Weź pod uwagę że wtedy ciężej byłoby pisać np framework do moków

I dobrze. To co musi być mokowane powinno mieć wydzielony interfejs.

2

Klasy pod anonimowe klasy są na ogół zaprojektowane do dziedziczenia, a ograniczenie mocków na plus.
Poza tym dużo kodu infrastruktury jest dostępnych przez interfejsy a te można zamocować, sfejkowac itd

1

@KamilAdam: > ##### KamilAdam napisał(a):

Weź pod uwagę że wtedy ciężej byłoby pisać np framework do moków

I dobrze. To co musi być mokowane powinno mieć wydzielony interfejs.

Noi kiedyś dokładnie tak robiono, interfejs, i dwie implementacje, jedna testowa, jedna produkcyjna. I zgadnij co. To było dużo niepotrzebnej pracy, bo zamiast dodatkowego interfejsu i klasy, można to opendzlować jednym when() z mockito. To są tylko testy. Poza tym czasem kod spodziewa się konkretnej implementacji jednego interfejsu - i co, potem zrobisz interfejs dziedziczący z interfejsu, żeby drugą implementacje otestować? 2/10.

Np spróbuj kiedyś zrobić wzorzec projektowy, np Bridge albo Visitor, albo coś innego co wymaga wielu interfejsów, i potem te klasy podziel z Foo na FooProd implements Foo i FooTest implements Foo, żeby nie używać mocków ;D Good luck with that!

PS: Wiem, że kiedy pisze się końcowe aplikacje, które są do siebie podobne, nie mają wielu interfejsów i są bardzo warstwowe (controller, model, view) to może się to wydawać ok. Ale spróbuj kiedyś usiąśc nie nad aplikacją, tylko na bilbioteką, frameworkiem, albo innym rozwiązaniem które nie jest końcowe. Takie które nie jest warstwowe, które zależy od wielu rzeczy i które ma zmienne implementacje; takie w którym obsłużenie czegoś to nie jest zmapowanie modelu na widok, tylko zaimplementowanie całego algorytmu albo rozwiązania. Wtedy zauważysz że dodatkowa warstwa (+ interfejs + implementacja testowa) to jest bardzo duży koszt, dużo większy niż klasa otwarta do otestowania mockito. I gdybyś w takim projekcie, np w Springu, w Hibernacie powidział: "Hej ludzie, usuńmy mockito, zróbmy wszystkie klasy final, i wszystko co chcemy otestować to dodajmy do każdego miejsca interfejs i implementację testową", zgadnij co byś usłyszał. (i nie mówię tu o nakładzie pracy, tylko o tym że takie rozwiązanie zwiększyłoby NIEPORÓWNYWALNIE skomplikowanie takiej libki). Jeśli są już dwie implementacje i interfejs, to połowę pracy masz z głowy, ale jeśli nie mają i jest to jedna implementacja (bo ma być jedna) to cóż.

1

Aplikacje powinno testować się integracyjnie a nie testować mockito. A nawet jak już testować jednostkowo to mockowac infrastrukturę a nie inne klasy z logiką biznesowa. Frameworków nie rozwijałem to się nie wypowiem.

0
TomRiddle napisał(a):

@KamilAdam: > ##### KamilAdam napisał(a):

Weź pod uwagę że wtedy ciężej byłoby pisać np framework do moków

I dobrze. To co musi być mokowane powinno mieć wydzielony interfejs.

Noi kiedyś dokładnie tak robiono, interfejs, i dwie implementacje, jedna testowa, jedna produkcyjna. I zgadnij co. To było dużo niepotrzebnej pracy, bo zamiast dodatkowego interfejsu i klasy, można to opendzlować jednym when() z mockito. To są tylko testy

Ale jak masz sam goły interfejs to na nim też możesz zastosować mockito. Wcale nie piszałem że trzeba pisać dedykowaną implementację do testów. Tutaj nawet jest tutorial jak mockować interface A Unit Testing Practitioner's Guide to Everyday Mockito. Niczym się to nie różni od mockowania klasy

Z drugiej strony jak to ma być proste when-then to zrobienie tego ręcznie (bez mockito) też jest proste, tylko niestety Java ma rozwlekłą składnię dla klas. Jakby ta składnia była bardziej zwięzła (jak w Scali czy Kotlinie) problemu takiego by nie było

1

Do wszystkich:

Weźcie proszę pod uwagę że final o które pytał autor wątku (pola, paramsy, zmienne lokalne) to nie jest to samo final do klas. Słowo kluczowe jest to samo, ale zastosowanie jest zupełnie inne.

0
KamilAdam napisał(a):

Ale jak masz sam goły interfejs to na nim też możesz zastosować mockito. Wcale nie piszałem że trzeba pisać dedykowaną implementację do testów. Tutaj nawet jest tutorial jak mockować interface A Unit Testing Practitioner's Guide to Everyday Mockito. Niczym się to nie różni od mockowania klasy

To i tak masz 2x więcej nazw do ogarnięcia. Poza tym weź pod uwagę, że skoro masz 1:1 interfejs i klasę, to to totalnie nic nie zmienia w architekturze. Nie jesteś w stanie wskazać jednej zalety mockowania interfejsu zamiast klasy. No oprócz tego że może być final. Co i tak nie ma znaczenia bo klasy final DA się zamockować, tylko jest to cięższe.

Jedyna zaleta którą myślę że zaproponujesz to to że jak KIEDYŚ będzie trzeba dodać kolejną implementacje, to już będzie: ale ta zaleta to nie zaleta bo nie kodzi się tak na przyszłośc, poza tym YAGNI.

4

Jest sens stosowania interfejsu z jedną implementacją, kiedy stosujemy clean architecture i oddzielamy port będący częścią domeny od implementacji która jest infrastrukturą.

0

W przypadku fasady przydają się finalne klasy?? :) słyszałem że można wydzielić finalne Klasy Service i wrzucić je do fasady :) podejście microservice...spotykacie to w firmach??

1
scibi92 napisał(a):

Jest sens stosowania interfejsu z jedną implementacją, kiedy stosujemy clean architecture i oddzielamy port będący częścią domeny od implementacji która jest infrastrukturą.

Bardzo ładnie brzmiące słowa, z tym że:

  • "Clean code" nigdzie nie mówi nic o wydzielaniu interfejsów z implementacji, tylko po to żeby je wydzielać. (Czytałem dwa razy po polsku i w oryginale, ale może coś przeoczyłem)?
  • Wydzielanie interfejsu z klasy tylko po to żeby była "architektura" nie ma żadnego znaczenia ani sensu, bo i tak jedna z nich jest stała (albo klasa albo interfejs). Jedyny "trochę" sens, jest wtedy kiedy interfejs jest upubliczniany, np wystawiany jako API biblioteki, albo sharowany pomiędzy modułami, ale wtedy nie jest to robione po to żeby jego implementacja była łatwiej testowana, wręcz przeciwnie bym powiedział, wtedy ten interfejs stanowi bardziej kontrakt, oprócz tego że pozwala na podmiankę impl. Wtedy, skoro API jest upuliczniane, wtedy tymbardziej testowałbym upublicznioną implementację w odseparowaniu, największym jakim się da. Jeśli natomiast to nie jest biblioteka, tylko zwykła końcowa apka dla end-usera, to tym bardziej nie ma to żadnego sensu. Jaki niby potencjalnie to podzielenie (po to żeby niby mieć "clean architekture"), jaką możliwą zaletę mogłoby to wprowadzić. W końcowej aplikacji "oddzielenie biznesowej domenty od infrastruktury" w tym samym produkcie co potencjalnie mógłbyś tym zyskać? Może sekundę lub dwie jeśli akurat ta jedna klasa będzie miała dwie implementacje (co się rzadko zdarza w końcowych aplikacjach). Tak na prawdę to jedyne co zyskasz to "sztukę dla sztuki", żeby móc używać takich buzzwordów "oddzieliłem domenę od infrastruktury". Ale dla jednej implementacji to nie ma sensu.

Jestem w stanie się założyć, że wszystkie zalety jakie jesteś w stanie znaleźć dla wydzielania int z impl mają tylko sens, jeśli implementacji jest więcej niż jedna.


Nie zrozumncie mnie źle, dla mnie clean code, architektura, solid, wzorce to jest super ważna rzecz. Dużo lepiej użyć kompozycji, i faktycznie dziedziczenie ssie (więc dobrze je nienadpisywać klas w logice), tutaj się zgadzamy.

Ale.

  • Żeby nie używać dziedziczenia nie trzeba dopisywać final do klas (można po prostu nie dziedziczyć, i tyle). A skoro nie dopiszemy final, to możemy użyć mocków ;)
  • Interfejsy są zajebistym narzędziem do rozwijania oprogramowania. Ale czy to się komuś podoba czy nie, ich zalety są bliskie 0, jeśli implementacji jest 1.

PS: Sorki, ale wydaje mi się że skoro rozpocząłeś taką wojnę jak "Nie róbcie mocków, lepiej dopisać interfejs do każdej implementacji, a ta implementacja powinna być final, to niestety musisz mieć baaaardzo dobre argumenty :D

5
scibi92 napisał(a):

Aplikacje powinno testować się integracyjnie a nie testować mockito. A nawet jak już testować jednostkowo to mockowac infrastrukturę a nie inne klasy z logiką biznesowa. Frameworków nie rozwijałem to się nie wypowiem.

Kiedyś tak odparłem na rozmowie, zaczęło się od TDD i piramidy testów.
Generalnie powiedziałem że testy jednostkowe rzadko kiedy mają sens i wolałbym się skupić na testowaniu integracyjnym, dla przykładu przy rest api od calla do sprawdzenia spójności danych w zamockowanej h2. Podobnie z TDD. pokrywany kod jakimiś dziwnymi mikrotestami jednostkowymi, po to żeby przy każdej pojedynczej modyfikacji pisać nowe testy i poprawiać stare. Bezsens.
Koniec końców tamci inżynierowie podsumowali że nie zgadzają się z tym, ale szanują moje zdanie i żebym tak nigdzie nie mówił bo mnie nigdzie do roboty nie przyjmą ;D
"dajemy panu taką wskazówkę..." XD

1
scibi92 napisał(a):

Aplikacje powinno testować się integracyjnie a nie testować mockito.

Odpowiednie narzędzie do odpowiedniego celu. Jeśli chcesz testować rozwiązanie bardziej horyzontalne (ogólne), niż szczegłowe to integracyjne będą lepsze. Jeśli chcesz bardziej otestować jakiś szczególny case, to unit testy będą dużo lepsze. Każde z nich ma swoje wady i zalety, dobrze jest dobrać odpowiednie do zadania.

A nawet jak już testować jednostkowo to mockowac infrastrukturę a nie inne klasy z logiką biznesowa. Frameworków nie rozwijałem to się nie wypowiem.

Tylko nie zapominaj że takie określenia jak "infrastruktura" i "logika biznesowa" to bardzo ogólne, user-friendly terminy; i im głębiej wejdziesz, tym bardziej one tracą sens. Np gdybyś pisał grę "Wiedźmin 3", klawisze do sterowania, to jest infrastruktura czy logika biznesowa? :D A ustawienia, ładowanie konfiguracji z pliku, tryb multiplier? Czy klasa która tłumaczy dialogi na inne języki to logika biznesowa czy architektura? Różne questy na różnych platformach?

Czasami zdarzają się problemy np czysto biznesowe, ale które wymagają wielu warstw do ich rozwiązania, i czasem zachodzi potrzeba przetestowania ich osobno, i co wtedy? Nie przetestujesz ich, bo to się kłóci z Twoim nazewnictem?

Poza tym jestem ciekaw jak chcesz powiedzieć jakimś 10 milionom programistów javy którzy używają mockito, że nie powinni tego robić.

Jeśli znajdziesz jakiś konkretny argument czemu wydzielanie niepotrzebnego interfejsu i finalowanie klas, zamiast użycie jednego when() jest lepsze, to Cię bardzo chętnie wysłucham. Ale na razie to wygląda jak powtarzenia wierszyków, które ładnie brzmią.

1

Tylko po co ten mock w ogole? Jedyne gdzie ma sens to baza danych, to to sensowniej użyć h2. W innym przypadku problem leży gdzie indziej (skopany dizajn) i to tylko leczenie objawowe.

2

Jeśli chcesz bardziej otestować jakiś szczególny case, to unit testy będą dużo lepsze. Każde z nich ma swoje wady i zalety, dobrze jest dobrać odpowiednie do zadania.

To są już naprawde przypadki szczegółowe. Poza tym testy jednostkowe nie są testami klas.

"Clean code" nigdzie nie mówi nic o wydzielaniu interfejsów z implementacji, tylko po to żeby je wydzielać. (Czytałem dwa razy po polsku i w oryginale, ale może coś przeoczyłem)?

Ja nie mówiłem o ksiażce "Clean Code" co najwyżej "Clean Architecture". Ale gdy mówie o "clean architecture" nie mam na myśli ksiażki tylko podejście w której domena jest najważniejszą częścią oprogramowania, i jest kilka terminów pokrewnych na to, np. Hexagonal Architecture (Ports and Adapters)

Załóżmy że masz aplikacje jaką jest kantor walutowy. Jest taki UseCase: zamieniasz PLN na EUR. Co wtedy potrzebujesz? Kursu wymiany. Więc masz taki interface:

interface ExchangeRateProvider {
   fun getExchangeRatio(from: Currency, to: Currency): Option<ExchangeRation>
}

To jest część domeny. a w jaki sposób ten kurs wymiany będzie dostarczony, to jest szczegół implementacyjny. Może to być np. klient restowy, SOAP, baza danych albo moga to byc dane trzymane w pamięci i aktualizowane przez Kafke!
Dzięki temu Twoja domena nie zależy od szczegółów technicznych (czy to baza danych, pliki, kolejka itp)
Tak naprawde Clean Architecture to taka nazwa fancy na Dependency Inversion Principle
Wyobraź sobie teraz że masz aplikacje w której potrzebujesz pobrać dostępność produktów. Na początek ta dostępność jest trzymana w bazie danych, ale jest wydzielona do jakiegoś mikroserwisu. Gdy Twoja domena nie zależy od tego szczegółu implementacyjnego, to po prostu podmeniasz implementacje szczegółu technicznego, a dla użytkownika jest to przeźroczyste

Jedyne gdzie ma sens to baza danych, to to sensowniej użyć h2.

@danek no chyba się nie zgodze, IMO duzo lepiej mieć TestContainers i używać tej bazy której stosujesz na produkcji. Jak mam postgresa to wole mieć na testach postgresa :P

1
scibi92 napisał(a):

Ja nie mówiłem o ksiażce "Clean Code" co najwyżej "Clean Architecture". Ale gdy mówie o "clean architecture" nie mam na myśli ksiażki tylko podejście w której domena jest najważniejszą częścią oprogramowania, i jest kilka terminów pokrewnych na to, np. Hexagonal Architecture (Ports and Adapters)

I czy w którym kolwiek z nich jest napisane że warto wydzielić interfejs z jednej implementacji, po to żeby był? Jeśli nie ma planu na dodanie kolejnych?

Załóżmy że masz aplikacje jaką jest kantor walutowy. Jest taki UseCase: zamieniasz PLN na EUR. Co wtedy potrzebujesz? Kursu wymiany. Więc masz taki interface:

interface ExchangeRateProvider {
   fun getExchangeRatio(from: Currency, to: Currency): Option<ExchangeRation>
}

To jest część domeny. a w jaki sposób ten kurs wymiany będzie dostarczony, to jest szczegół implementacyjny. Może to być np. klient restowy, SOAP, baza danych albo moga to byc dane trzymane w pamięci i aktualizowane przez Kafke!

Tak, ale w sytuacji w której nie ma takich planów, i masz tylko jedną implementację, to to nie ma sensu. Dlatego że:
a) Jeśli masz jedną implementacje, to zawsze używasz tej jednej i ten interfejs nie daje żadnej korzyści
b) Jeśli nawet będziesz chciał dodać nową implementacje (soap, rest, coś innego, bla, bla) to nie wiesz na jakim poziomie to dodać (wyjątkiem jest kiedy masz jeden poziom, tzn tylko jedną klasę która to garnia, czyli ten ExchangeRateProvider).

Dzięki temu Twoja domena nie zależy od szczegółów technicznych (czy to baza danych, pliki, kolejka itp)

Jeśli schowasz całą logikę w klasie ExchangeRateProvider to też nie będzie zależeć.

Wyobraź sobie teraz że masz aplikacje w której potrzebujesz pobrać dostępność produktów. Na początek ta dostępność jest trzymana w bazie danych, ale jest wydzielona do jakiegoś mikroserwisu. Gdy Twoja domena nie zależy od tego szczegółu implementacyjnego, to po prostu podmeniasz implementacje szczegółu technicznego, a dla użytkownika jest to przeźroczyste

A jeśli nie masz interfejsu, to klikasz skrót Ctrl+Alt+I (extract interfejs) i masz DOKŁADNIE ten sam efekt. Tylko z tą różnicą, że teraz masz konkretny powód do wydzielenia tego interfejsu.

Ja po prostu mówię: Poczekaj z wydzieleniam interfejsu do czasu kiedy masz powód.
Ty mówisz: wydziel interfejs z każdej klasy.

2

I czy w którym kolwiek z nich jest napisane że warto wydzielić interfejs z jednej implementacji, po to żeby był? Jeśli nie ma planu na dodanie kolejnych?

Tak, bo interface to kontrakt.

Jeśli schowasz całą logikę w klasie ExchangeRateProvider to też nie będzie zależeć.

Będzie, bo wtedy nie możesz już trzymać ExchangeRateProvider wraz z pakietem mającym tylko klasy związane z domeną. Musisz mieć albo klienta restowego w domenie, albo domene importującą pakiety z infrastruktury. A tak możesz mieć całą domenę niezależną od frameworków, baz danych, klientów resta, klientów kafki itp

1

@scibi92: > ##### scibi92 napisał(a):

I czy w którym kolwiek z nich jest napisane że warto wydzielić interfejs z jednej implementacji, po to żeby był? Jeśli nie ma planu na dodanie kolejnych?

Tak, bo interface to kontrakt.

Klasy bez wydzielonego interfejsu również zapewniają kontrakt.

Jeśli schowasz całą logikę w klasie ExchangeRateProvider to też nie będzie zależeć.

Będzie, bo wtedy nie możesz już trzymać ExchangeRateProvider wraz z pakietem mającym tylko klasy związane z domeną. Musisz mieć albo klienta restowego w domenie, albo domene importującą pakiety z infrastruktury. A tak możesz mieć całą domenę niezależną od frameworków, baz danych, klientów resta, klientów kafki itp

Teraz jestem przekonany że mówisz o warstwowych, aplikacjach końcowych, a nie o rozwoju oprogramowania w ogóle (tzn aplikacjach które trafiają od razu do usera, od których nic nie zależy (i nie mówię tu o interfejsach np http, etc. mam na myśli "zależy kodowo"), i w których boilerplate jest duży i warstwy to jest spora część aplikacji, a logika jest mniejszą).

No to faktycznie, jeśli masz z góry narzucone warstwy, i masz osobne pakiety które np chciałbyś deployować/dystrubutować osobno, to może i to ma sens dla ExchangeRateProvider. Może. Dlatego że:
a) Jeśli nie chcesz ich dystrybutować osobno, to to nic złego że domenowy pakiet ma jednego importa z zewnętrznego pakietu (jeśli implementacja jest jedna).
b) wybrałeś sobie przykład, w którym jest bardzo łatwo określić jakie/czy/gdzie mogłyby być dostarczone inne implementacje (provider walut, bardzo wygodne).

Poza tym nadal nie odniosłeś się do mojego poprzedniego pytania, co w styacji kiedy logika jest bardzo złożona i granica pomiędzy (tym co nazwałeś) "infrastrukturą" i tym co nazwałeś "domeną" się zaciera. Bo oczywiście, kiedy piszesz apkę dla usera podpartą libkami i frameworkami to logiki masz bardzo mało, i łatwo sobie powiedzieć: "ta klasa to domena, ta klasa to infrastruktura". Ale kiedy tych klas jest dużo więcej, jak np w jakiejś konkretnej branżowej skomplikowanej logice, to rozróżnienie się zaciera.

Nie chcę mi się filozować, więc podsumuję to tym:

Programowanie to dosyć skomplikowana i nowa dziedzina, dużo jest tutaj zmiennych, i bardzo dużo zależy od wielu innych rzeczy.
To, dlaczego prowadzę z Tobą cały ten wątek to, to że potrzebowałbym bardzo dobrego argumentu, żeby się zgodzić z Twoim bardzo jednostronnym i bezwarunkowym stanowiskiem, że zawsze należy wydzielić interfejs z każdej klasy, nawet jeśli ma jedną implementację. Bo Twoja teza nie brzmiała "czasem warto", albo "w konkretnych przypadkach", albo "ja tak robię".

Programowanie to nie tylko backendy z urlami /user/accounts, CRUD, mappery, widoki i repozytoria z baz danych (takie apki każdy po kursie na udemy może napisać). Ale zaprojektuj np edytor tekstu (podobny do worda), albo painta, odtwarzacz wideo, grę w szachy online, program wizualizujący fractale, narysuj drzewo w opengl'u, program konwertujący svg na png, klienta http jak curl, nakładkę na osa która wyświetla ikonki przy folderach - w skrócie jaką kolwiek aplikacje w której logiki jest dużo, a warstw jest mało. I wtedy wypowiedz się, ile razy w takich aplikacjach pojawił się problem oddzielenia domeny od infrastruktury.

2
scibi92 napisał(a):

Tak, bo interface to kontrakt.

Dodawanie interfejsu i jego jednej implementacji po to tylko, żeby mieć jakiś rozdział na abstrakcję i implementację to antywzorzec taki sam, jak jednostkowe testowanie działania Springa. Jaki niby z tego uzysk? Że możesz sobie dopisać inną klasę, której nigdy nie dopiszesz? To mi pachnie takim projektowaniem "na zaś", a takie podejście to już jest wstęp do dziesiątek problemów.

3

To, dlaczego prowadzę z Tobą cały ten wątek to, to że potrzebowałbym bardzo dobrego argumentu, żeby się zgodzić z Twoim bardzo jednostronnym i bezwarunkowym stanowiskiem, że zawsze należy wydzielić interfejs z każdej klasy, nawet jeśli ma jedną implementację. Bo Twoja teza nie brzmiała "czasem warto", albo "w konkretnych przypadkach", albo "ja tak robię".

@TomRiddle :
niegdzie tak nie napisałem. Napisałem że takie jest podejście w pewnej konkretnym typie architektury która świetnie się nadaje do aplikacji biznesowych, I nie napisałem o każdej klasie, tylko tej klasie która jes na pograniczu styku domeny i infrastruktury. Jeśli masz jakąs klase pomocniczą w infrastrukturze która nie jest używana przez domene (bo nie jest implementacją kontraktu) nie potrzebny jest interface. Tak samo jak w kodzie domenowym nie jest potrzebny inferface UseCas'ow czy serviców domenowych.

Programowanie to nie tylko backendy z urlami /user/accounts, CRUD, mappery, widoki i repozytoria z baz danych (takie apki każdy po kursie na udemy może napisać). Ale zaprojektuj np edytor tekstu (podobny do worda), albo painta, odtwarzacz wideo, grę w szachy online, program wizualizujący fractale, narysuj drzewo w opengl'u, program konwertujący svg na png, klienta http jak curl, nakładkę na osa która wyświetla ikonki przy folderach - w skrócie jaką kolwiek aplikacje w której logiki jest dużo, a warstw jest mało. I wtedy wypowiedz się, ile razy w takich aplikacjach pojawił się problem oddzielenia domeny od infrastruktury.

Mówilem o specyficznym typie architektury która ma zastosowanie głównie do aplikacji enterprise. Być może nie wyraziłem się wcześniej wystarczająco prezycyjnie, być może dlatego że założyłem domyślnie że skoro piszemy o Javie to piszemy o takich systemach. W Javie gier się nie pisze tak samo jak nie pisze się Exceli.

b) wybrałeś sobie przykład, w którym jest bardzo łatwo określić jakie/czy/gdzie mogłyby być dostarczone inne implementacje (provider walut, bardzo wygodne).

Wybrałem przykład z mojego życia programistycznego.

a) Jeśli nie chcesz ich dystrybutować osobno, to to nic złego że domenowy pakiet ma jednego importa z zewnętrznego pakietu (jeśli implementacja jest jedna).

Łamie to Dependency Inversion Principle. Przy czym nie jest to zawsze problem. Jeśli masz system bankowy na 200k lini to lepiej nie łamać, a jak masz mały serwis infrastrukturalny który pobiera emaile z kolejki i je wysyła to tam DIP nie ma tak dużego znaczenia.

0
scibi92 napisał(a):

Jeśli masz jakąs klase pomocniczą w infrastrukturze która nie jest używana przez domene (bo nie jest implementacją kontraktu) nie potrzebny jest interface. Tak samo jak w kodzie domenowym nie jest potrzebny inferface UseCas'ow czy serviców domenowych.

No tak, dokładnie to chciałem wykazać.

To jest ten case w którym nie ma sensu wydzielanie interfejsów.

a) Jeśli nie chcesz ich dystrybutować osobno, to to nic złego że domenowy pakiet ma jednego importa z zewnętrznego pakietu (jeśli implementacja jest jedna).

Łamie to Dependency Inversion Principle. Przy czym nie jest to zawsze problem. Jeśli masz system bankowy na 200k lini to lepiej nie łama, a jak masz mały serwis infrastrukturalny który pobiera emaile z kolejki i je wysyła to tam DIP nie ma tak dużego znaczenia.

Dependency Inversion Principle nie mówi nic o tym w jakim pakiecie mają być zależności. Mówi o tym że klasa powinna dostać zależność "z góry", a nie sama ją sobie dostarczyć.

1

@scibi92: > ##### scibi92 napisał(a):

Mówilem o specyficznym typie architektury która ma zastosowanie głównie do aplikacji enterprise. Być może nie wyraziłem się wcześniej wystarczająco prezycyjnie, być może dlatego że założyłem domyślnie że skoro piszemy o Javie to piszemy o takich systemach. W Javie gier się nie pisze tak samo jak nie pisze się Exceli.

No wiadomo, taki Minecraft albo Idea to architektura enterprise :)
Nawet jeśli zostaniemy przy architekturze enterprise - to nie są to tylko biznesowe CRUDy gdzie architektura heksagonalna/trójwarstwowa to mus. Pisząc coś na Kafka Streams nie będziesz silił się na to, żeby uniezależnić warstwę logiki od Kafki.

1

Dependency Inversion Principle nie mówi nic o tym w jakim pakiecie mają być zależności. Mówi o tym że klasa powinna dostać zależność "z góry", a nie sama ją sobie dostarczyć.

Chyba mylisz Dependency Inversion z Dependency Injection :P

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions

To jest dependency inversion

@wartek01
Przecież napisałem:

a jak masz mały serwis infrastrukturalny który pobiera emaile z kolejki i je wysyła to tam DIP nie ma tak dużego znaczenia.

Poza tym Minecraft czy IntelliJ to są wyjątki

0

@scibi92: no ale operacje na Kafka Streams czy jakimś innym Flinku już się chyba do wyjątków nie zaliczają? Znam co najmniej dwa duże banki gdzie sporo kodu na tym siedzi.

1

po dwoch latach w nowym javowym projekcie mozna powiedziec ze wszyscy stosujemy prawie identyczne podejscie, final:

  • praktycznie zawsze na polach i dla zmiennych lokalnych bo dzieki temu od razu widac zmienny stan (rzadkosc, ale jednak)
  • praktycznie nigdy na parametrach metod, samych metodach i klasach bo generalnie nie bawimy sie w dziedziczenie

w przeszlosci sklanialam sie do wrzucania final wszedzie gdzie sie da, ale teraz wydaje mi sie ze powyzsze podejscie jest optymalne, bo generalnie nikt normalny nie nadpisuje sobie parametrow czy implementacji poza testami

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