Tworzenie nadmiarowych interfejsów.

Tworzenie nadmiarowych interfejsów.
O1
  • Rejestracja:ponad 14 lat
  • Ostatnio:8 dni
0

Witam. Mam pytanie dotyczące waszego podejścia w kwestii tworzenia klas oraz interfejsów do nich. Załóżmy, że mamy aplikację napisaną w Javie z użyciem Springa. Często spotykałem się z tym, że dla każdej klasy był tworzony interfejs do niej, nawet jeśli implementowała go tylko ta jedna klasa. Jakie jest wasze podejście w tej kwestii. Załóżmy, że mamy klasę UserSevice, która jest serwisem wykonującym, różne operacje na użytkowniku. Czy tworzycie wtedy do takiej klasy interfejs, nawet jeśli będzie go implementowała tylko jedna klasa? Jak wtedy takie klasy nazywacie? Najczęściej spotkałem się z interfejsem o nazwie UserService a jego implementacją UserServiceImpl. Osobiście nie przekonuje mnie takie podejście jak i wynikając z niego nazewnictwo i moim zdaniem jeśli interfejs ma mieć tylko jedną implementację to nie ma sensu go tworzyć, zwłaszcza, że w Springu, możemy wstrzykiwać po klasie. Moim zdaniem tworzenie interfejsu ma tylko sens jeśli klasa będzie jakimś API, które będzie udostępniane na zewnątrz i z którego będą korzystać inne systemy, lub będzie faktycznie zachodziła relacja dziedziczenia np. GenericDao z kilkoma "gotowymi" metodami i jego implementacje czy interfejs Transfer i kilka implementacji typu: NormalTransfer, ExpressTransfer. Jakie jest wasze podejście w tej kwestii?

krzysiek050
  • Rejestracja:ponad 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:1272
4

Powody są 2:

  1. Stare wersje JEE wymagały od enterprise beanów interfejsów, więc trzeba było tak robić.
  2. Jest zasada żeby nie dało się odróżnić implementacji od interfejsu z punktu widzenia użytkownika.

Ad 1) Jeżeli kiedyś cokolwiek trzeba było, a już nie trzeba, to myślę że nie ma sensu brać tego do siebie. Gorsze czasy tutaj raczej nie wrócą.
Ad 2) I tworzenie sztucznego interfejsu łamie ją, bo zwykle robi się IJakiśinterfejs lub JakaśImplementacjaImpl.

Jeżeli ktoś w 2017 roku dalej widzi potrzebę tworzenia interfejsu bo tak, to najpewniej jest dziadkiem który utknął w starych czasach. Jako że taki dziadek jest zwykle seniorem i wyżej, to zwykle ma dużo do powiedzenia w zasadach rozwijania projektu i jest wzorem (heh) dla młodych którzy robią tak samo nie wiedząc dokładnie po co to robią.

edytowany 1x, ostatnio: krzysiek050
neves
  • Rejestracja:prawie 22 lata
  • Ostatnio:około 16 godzin
  • Lokalizacja:Kraków
  • Postów:1114
2

Również nie jestem zwolennikiem programowania do interfejsów, które są w relacji jeden do jednego z klasą, jeśli nie ma takiej potrzeby, bo jest to zbędna abstrakcja która wprowadza niepotrzebną złożoność.

Natomiast zacznie ciekawsze jest kwestia kiedy interfejsy naprawdę są potrzebne:

  • tak jak już zauważyłeś, na granicach komponentów
  • kiedy potrzebujemy zrobić jakąś bardziej wyszukaną zaślepkę w testach
  • kiedy robimy prawdziwe Dependency inversion & Interface segregation, gdzie to moduły wysokopoziomowy definiuje abstrakcje której potrzebują
  • kiedy potrzebujemy wprowadzić extension point

i pewnie o czymś zapomniałem :)


M9
  • Rejestracja:prawie 10 lat
  • Ostatnio:około 6 lat
2

Interfejsy tylko wtedy, gdy jest więcej jak 1 implementacja. Chyba, że trzeba rzeźbić w EJB 3.0. Tam były wymagane, ale to naprawili (w EJB 3.1+).

edytowany 1x, ostatnio: margor90
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
2

Ja też jestem przeciwnikiem wyodrębniania interfejsów bo tak. Jeśli jest jedna implementacja produkcyjna to interfejsu po prostu nie trzeba. Nawet argument z testowaniem nie jest przekonujący, bo jeśli tworzenie klasy z np nullowymi zależnościami powoduje wysypanie się kodu to prawdopodobnie znaczy, że popełniliśmy błąd i konstruktor robi za dużo: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/ Pracę potrzebną do zainicjalizowania obiektu (oprócz oczywiście przepisania pól, które trzeba zrobić w środku docelowej klasy) trzeba wydzielić do jakiejś metody fabrycznej lub tego typu osobnego miejsca. Fabrykę wtedy testujemy w innym teście niż tworzoną przez nią klasę.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 16 godzin
2
Wibowit napisał(a):

jeśli tworzenie klasy z np nullowymi zależnościami powoduje wysypanie się kodu to prawdopodobnie znaczy, że popełniliśmy błąd i konstruktor robi za dużo

Dlaczego chcesz do konstruktora wpychać nulle? Powinny tam być mocki. Jeśli do konstruktora nie wsadzisz mocków, to będziesz musiał kod testować albo integracyjnie, albo będziesz skazany na wstrzykiwanie zależności w niestandardowy sposób, np. upubliczniając settery, co prowadzi do zepsucia hermetyzacji, albo grzebiąc w prywatnych danych poprzez refleksję (fuj).
Konstruktor powinien doprowadzić obiekt do stanu używalności, żeby nie trzeba było go dodatkowo inicjalizować wywołując jakąś metodę init() czy coś na jej wzór ...Acz w jednej z dyskusji na 4p spotkałem się z podejściem, w którym konstruktor nie robi nic, żeby nie wpłynąć na stan obiektu, ma go tylko stworzyć i już, a faktyczną inicjalizację robi osobna metoda, co jest o tyle problematyczne, że łatwo zapomnieć o wywołaniu dodatkowej metody, a samo podejście pachnie mi sztuką dla sztuki. A więc jeśli obiekt do stanu, w którym można go użyć wymaga wielu operacji, to albo konstruktor musi te operacje przeprowadzić, albo - co moim zdaniem jest sensowniejsze - należy klasę i/lub całą funkcjonalność przeprojektować.
Interfejsy wprowadzają dodatkową warstwę abstrakcji, która w dużych systemach bywa niezbędna, ale w małych faktycznie może tylko zagmatwać kod. Użycie interfejsów wydaje mi się zasadne w momencie użycia kontenera IoC, acz można to opędzić i klasą abstrakcyjną, i zwykłą klasą.


edytowany 1x, ostatnio: ŁF
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
2

Kontener DI nie wymaga interfejsów. Można spokojnie bindować klasę do samej siebie i Wszechświat nie wybucha. A nawet gdyby wymagał to da się napisać jakiegoś kikuta tylko po to by dziedziczyć, a więc napisać klasę abstrakcyjną, która robi wszystko, a potem dopisać klasę dziedziczącą która ma tylko konstruktor delegujący do konstruktora z klasy wyższej.

Konstruktor powinien prawie zawsze robić tylko jedną rzecz - przepisywać parametry do składowych klasy. Tyle. Jeśli wstawię tam nulle (bo nie chce mi się robić mocków czy stubów, a wiem że te zależności nie mogą być użyte w danym teście, więc sens podawania nulli jest) to te nulle będą tylko skopiowane do pól wewnętrznych, a więc nic strasznego nie może się stać. Ergo - tworzenie klasy podając nulle jako parametry powinno co najwyżej stworzyć klasę z niespójnym stanem, ale tworzenie tej klasy powinno się powieść.

Polecam poczytać artykuł Misko Hevery'ego z mojego poprzedniego posta.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
TD
Ja zazwyczaj używam checkNotNull w konstruktorze więc by się wysypało. To błąd?
Wibowit
W sumie to musiałbym popatrzeć na kod całej aplikacji by stwierdzić co ma sens. W Scali nie używa się nulli tylko Optionów, a jeśli korzystamy z jakiegoś API Javowego, które może zwrócić nulla to od razu jest konwersja na Option, np Option(file.listFiles()) i tym samym od razu pozbywamy się potencjalnego nulla. Skoro nie używa się nulli w kodzie produkcyjnym w Scali to też się ich nie sprawdza i to pozwala iść na skróty w testach. W Javie jest Optional, ale szanse są nikłe by w najbliższej przyszłości stało się to standardem, więc Javowcy zostają z Objects.requireNonNull.
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 16 godzin
1

Ogólnie zgadzam się z przedstawionymi tam przemyśleniami, ale:

Testing Directly is Difficult
Testing such constructors is difficult. To instantiate an object, the constructor must execute. And if that constructor does lots of work, you are forced to do that work when creating the object in tests. If collaborators access external resources (e.g. files, network services, or databases), subtle changes in collaborators may need to be reflected in the constructor, but may be missed due to missing test coverage from tests that weren’t written because the constructor is so difficult to test. We end up in a vicious cycle.

To dotyczy każdej metody, nie tylko konstruktora. Tyle w tym temacie.

It Forces Collaborators on You
Sometimes when you test an object, you don’t want to actually create all of its collaborators. For instance, you don’t want a real MySqlRepository object that talks to the MySql service. However, if they are directly created using new MySqlRepositoryServiceThatTalksToOtherServers() inside your System Under Test (SUT), then you will be forced to use that heavyweight object.

Ktoś tu o DI i mockowaniu nie słyszał (artykuł z 2008 roku!).

It Still is a Flaw even if you have Multiple Constructors (Some for “Test Only”)
Creating a separate “test only” constructor does not solve the problem. The constructors that do work will still be used by other classes.

Tak się po prostu nie robi. Konstruktor specjalnie dla testów oznacza kompletne spieprzenie architektury, ponadto test nie sprawdza wtedy właściwej ścieżki, bo omija stosowany na produkcji konstruktor, a to oznacza dziurę nieprzykrytą testami. Niestety żyjemy w świecie, gdzie utrzymujemy duże, długo rozwijane projekty oparte o rozwiązania, które były projektowane w czasach, kiedy nie było mocków, np. starsze MVC w .net i takie kwiatki się zdarzają, sam kilka popełniłem z całą świadomością ich ułomności.
Co do części The constructors that do work will still be used by other classes., to nieprawda, DI i mocki załatwią sprawę.

Autorzy nie wspomnieli o wydajności testów, a ta jest ważna, jeśli mamy ich tysiące oraz mamy CI i te tysiące testów trwają po kilkadziesiąt minut. Może za tym stać konstruktor, który robi dużo rzeczy, których nie da się zamockować; takie rzeczy są czasochłonne, a to oznacza, że skonstruowanie tylu instancji ile jest testów (i test case'ów) może zająć istotnie długi czas. Czasy testów się wydłużają, nie wystarcza agentów, na których testy są uruchamiane, kolejka rośnie, trzeba czekać... (Testy ofc możesz, a nawet powinieneś odpalić lokalnie przed commitem, ale różnie z tym bywa w praktyce, zwłaszcza jeśli testy trwają bardzo długo).


edytowany 1x, ostatnio: ŁF
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
1

W artykule mocki wspomniane są wielokrotnie. To nie jest artykuł z epoki dinozaurów. EasyMock powstał w roku 2001, więc w momencie pisania artykułu idea automatycznego mockowania w Javie miała co najmniej około 7 lat.

Tak się po prostu nie robi. Konstruktor specjalnie dla testów oznacza kompletne spieprzenie architektury, ponadto test nie sprawdza wtedy właściwej ścieżki, bo omija stosowany na produkcji konstruktor, a to oznacza dziurę nieprzykrytą testami.

No tutaj potwierdzasz słowa Hevery'ego.

Co do części The constructors that do work will still be used by other classes., to nieprawda, DI i mocki załatwią sprawę.

Wyjąłeś to z kontekstu. Hevery mówił o przypadku gdzie robimy konstruktory wykonujące pracę i one chodzą na produkcji oraz obok konstruktory specjalnie do testów. Taka sytuacja jest niedopuszczalna, pisał tak wyraźnie w artykule.

To dotyczy każdej metody, nie tylko konstruktora. Tyle w tym temacie.

Mogę stworzyć obiekt bez wywoływania metody, ale nie mogę wywołać metody bez stworzenia obiektu. Stąd tworzenie obiektu (czyli wywołanie konstruktora) nie może pociągać realnej pracy i w zasadzie o tym jest cały artykuł.

Gwoli wyjaśnienia - constructors that do real work to konstruktory, które łamią zasadę wstrzykiwania zależności. Real work to tutaj tworzenie tychże zależności i (opcjonalnie) zmiana ich stanu.

A z wstrzykiwaniem nulli to chodziło mi o to, że jeśli test sprawdza czy dana zależność ma być nietykana w trakcie tego testu to zamiast robić mocka i potem weryfikować, że nic na nim nie zostało odpalone to mogę wstawić po prostu nulla i jeśli klasa zechce skorzystać z tej zależności to dostaniemy NullPointerException i test się (zgodnie z zamysłem autora) sypnie.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 4x, ostatnio: Wibowit
SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
1

Interfejs może mieć taką zaletę, że można go zmokować pisząc implementację manualnie bez używania zewnętrznego frameworka.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
1

Klasę też można pod warunkiem, że konstruktor nie robi realnej pracy tylko przepisuje zależności do pól. Oto przykład klasy, w której konstruktor nie wykonuje żadnej realnej pracy oprócz przepisywania zależności do pól (kod w Scali):

Kopiuj
class Klaska(zależność1: Zależność1, zależność2: Zależność2) { // Scala daje tutaj fajny cukier składniowy, parametry głównego konstruktora od razu przypisywane są do pól o takich samych nazwach
  def metodka1(): Unit = {
    // tutaj używamy zależność1 i zależność2
  }
}

W testach możemy zrobić sobie ręcznie mocka:

Kopiuj
 class MockKlaski(testoweUstrojstwo: TestoweUstrojstwo) extends Klaska(null, null) { // można też wstawić coś innego niż null w razie potrzeby
  override def metodka1(): Unit = {
    // tu możemy robić co się nam żywnie podoba używając np testowego ustrojstwa
  }
}

Jeśli chcemy, by nasza klasa miała zainicjowany i spójny stan to robimy sobie metodę fabrykującą w companion object'u (w Javie byłaby zwykła metoda statyczna):

Kopiuj
object Klaska {
  def apply(zależność1: Zależność1): Klaska = {
    zależność1.przygotuj();
    val zależność2 = zróbZależność2()
    new Klaska(zależność1, zależność2)
  }
}

W taki oto sposób mogę sobie instancjonować miliony instancji klasy Klaska, a także dowolnie po niej dziedziczyć w testach zachowując wygodną testowalność.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 16 godzin
1
student pro napisał(a):

Interfejs może mieć taką zaletę, że można go zmokować pisząc implementację manualnie bez używania zewnętrznego frameworka.

Nie widzę w tym żadnej zalety. Pisanie do tego własnej implementacji to wyważanie otwartych drzwi. Frameworki do testów jednostkowych i do mocków od lat zawierają praktycznie wszystko, czego trzeba. Pamiętaj, że kod odpowiedzialny za testy nie musi lądować na produkcji, więc tych bibliotek nie trzeba tam dołączać. Pamiętaj, że testy trzeba też utrzymywać, więc nie powinny zawierać kodu, który jest zbędny (chyba, że poprawia to czytelność pewnych fragmentów).
Ale może nie mam racji i czegoś nie widzę, podpowiesz dlaczego widzisz zaletę własnej implementacji mocka?


SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0

Po prostu mniej dodatkowych bibliotek/zależności, ale to taka dyskusyjna zaleta, nie nalegam ;)

SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0
Wibowit napisał(a):

Klasę też można pod warunkiem, że konstruktor nie robi realnej pracy tylko przepisuje zależności do pól. (...)

No i pod warunkiem że nie ma problemu z finalami. Ale czemu nie zrobić w takim razie bezargumentowego konstruktora protected?

ŁF
Po co? Co to ma wspólnego z "nadmiarowymi" interfejsami? Co miałby dać taki konstruktor?
SP
@ŁF W tym przykładzie który podał @Wibowit jeżeli do klasy Klaska dodam bezargumentowy konstruktor (protected), to MockKlaski może po niej dziedziczyć a konstruktor Klaski z argumentami dalej może pilnować nulli
ŁF
Tak się nie robi... To znaczy robi się - ale tylko, jeśli nie ma innego wyjścia, bo powoduje to potrzebę opakowywania klas do testów w dodatkowe klasy. Po co? Taką klasę trzeba napisać i utrzymywać, to jest czas i pieniądze oraz miejsce na kolejne błędy. A Ty jeszcze proponujesz to jako rozwiązanie...
0
margor90 napisał(a):

Interfejsy tylko wtedy, gdy jest więcej jak 1 implementacja. Chyba, że trzeba rzeźbić w EJB 3.0. Tam były wymagane, ale to naprawili (w EJB 3.1+).

W prostych programach tak.

Ale w bardziej złożonych skąd wiesz ile będzie potrzebnych implementacji danej klasy ?

Czy za 2 lata lub 5 lat dalej będzie potrzebna jedna implementacja ?

Interfejsy są wybieganiem w przyszłość, gwarancją, że nie trzeba będzie przebijać się przez miliony linii kodu w celu zmiany referencji do tej jednej klasy.

jarekr000000
To co napisałeś to jedna z recept jak szybko zrobić z prostego systemu koszmar. YAGNI!
krzysiek050
  • Rejestracja:ponad 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:1272
1

@Bogaty Terrorysta Zdajesz sobie sprawę że zrobienie tego w nowoczesnym IDE to kilka kliknięć? A dodawanie rzeczy których nie potrzebujesz tylko zaśmieca kod.

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 12 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2

Dawno temu miałem kolegę, który do klas w Javie dorzucał pola

Kopiuj
private Object reserved1;  //na wypadek gdyby kiedyś były potrzebne 
private Object reserved2;  //reserved for future use
private Object reserved3; 

(Przy okazji ten koncept pochodzi z języka C (z bibliotek) i tam miał trochę sensu.

Dodawanie interfejsów, bo kiedyś możesz mieć inną implementację, ma mniej więcej taki sam sens jak reserved1 w javie.


jeden i pół terabajta powinno wystarczyć każdemu
Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
2

Interfejsy służą do definiowania protokołów komunikacji pomiędzy klasami. I są w tym zdecydowanie lepsze niż bezpośrednio używane klasy. Można bez nich się obejść, ale tylko wtedy, gdy nie chcemy gadać z elementami spoza naszego podwórka. Przykładowo w nie publicznych klasach wewnętrznych.

Tworzenie obiektów

Konstruktor powinien przyjmować jako parametry wszystkie wymagane „podzespoły”. Inaczej mówiąc, wprost definiujemy wymagane zależności na poziomie semantycznym. Wtedy nie ma potrzeby używania DI. DI będzie tylko narzędziem ułatwiającym życie. Dlaczego zatem interfejsy? Ponieważ tworząc obiekt, nie powinniśmy wiązać się z implementacją jego zależności. Jeżeli tworzę jakąś zależność, to dobrze jest, gdy zdefiniujemy wspólny sposób komunikacji. W przeciwnym wypadku jakakolwiek zmiana w zależności, jak i zależnościach tranzytywnych, może okazać się zabójcza dla naszego kodu.

Testowanie i mockowanie

Jeżeli mogę utworzyć obiekt w jedyny „słuszny” sposób, to przystępując do testowania, wiem, czego potrzebuję. Jeżeli opieram zależności o interfejsy, to znacznie łatwiej jest mi zarządzać testem. W dodatku mogę wtedy skupić się na testowaniu mojego kodu, a nie pomaganiu Szczepanowi Faberowi w testowaniu Mockito. Dobrze opisuje to Jose Valim. W przypadku tego typu podejścia nie musimy zwracać uwagi na elementy finalne. W dodatku jawna deklaracja wszystkich zależności pozwala na uniknięcie zależności statycznych.

Techniki

Wbrew pozorom tworzenie interfejsów nie jest aż tak czasochłonne. Idea, Eclipse, Netbeans posiadają odpowiednie refaktoryzacje w rodzaju „extract interface”. Jeżeli wykorzystujemy TDD w naszej pracy, to bardzo naturalną metodą pracy jest zdefiniowanie interfejsu. Podobnie ma się rzecz w przypadku projektowania zgodnie z DDD.

Jeszcze inną metodą jest tworzenie kodu zgodnie z zasadami Precision Parameter Passing:

Pozwala ona na komponowanie serwisów tak, by minimalizować ryzyko błędów. Zresztą tę technikę wykorzystuje się w czasie pracy z AngularJS, gdzie do serwisów jawnie przekazujemy WSZYSTKIE zależności.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
krzysiek050
  • Rejestracja:ponad 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:1272
0

@Koziołek:
Zgadzam się że sam interfejs jest ważny i to jego zawartość powinna być istotna a nie sama implementacja.
Uważam jednak że definiując klasę, ona sama z siebie posiada pewien interfejs, a dokładniej nazwę i metody publiczne. Jeżeli w IDE zwiniemy klamry to widok będzie podobny do tego z interfejsu. Dlatego pisząc klasę, da się określić jej interfejs bez wystawiania go jawnie. Ktoś kto użyje klasy czy to bezpośrednio czy to przez interfejs nie powinien w żadnym wypadku widzieć różnicy. Dlatego stworzenie interfejsu do 1 klasy jest dla mnie po prostu nadmiarowym opisem. Skoro dobre IDE potrafi zrobić to sam po wybraniu odpowiedniej opcji to znaczy że jest do akcja deterministyczna i posiadasz wszystkie informacje do jej wykonania, czyli nadmiarowa.

S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
0

A ja myślałem że chodzi o łatwe stworzenie proxy. Ale może się mylę :D


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
2

Ktoś kto użyje klasy czy to bezpośrednio czy to przez interfejs nie powinien w żadnym wypadku widzieć różnicy.

@krzysiek050: No nie do końca, ponieważ używając interfejsu interesują mnie tylko pewne zachowania, które są określone w tym interfejsie. Używając klasy mam dostęp do wszystkich zachowań. Świetnie widać, to w przypadku Springa i hierarchii CRUD Dao. Możesz na poziomie serwisu napisać, że chcesz CrudDao i mieć dostęp tylko do podstawowych operacji. Możesz powiedzieć, że chcesz konkretne Dao i mieć dostęp do specyficznych mechanizmów takiego interfejsu.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
krzysiek050
  • Rejestracja:ponad 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:1272
0

@Koziołek
To już głębszy przykład, bo takie coś ma miejsce jeżeli jest wiele implementacji. Jeżeli jest jedna, to możesz co najwyżej rozbić zachowania klasy na kilka interfejsów które ograniczą część jej możliwości. Ale jeżeli jedna klasa ma kilka interfejsów z których tylko ona używa, to znaczy że dałoby się ją rozbić na mniejsze części i powstałyby pary 1 interfejs - 1 klasa, czyli wracamy do punktu wyjścia.

vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

TL;DR

Robienie do wszystkiego interfejsów może być trochę durne (zwłaszcza jak jest jedna implementacja).
Proponuję prostą zasadę: interfejs do wszystkiego co wstrzykujemy (ale dzisiaj, a nie co moglibyśmy ew. wstrzykiwać).
A klasy abstrakcyjne tylko do dziedziczenia - o ile mają jakąś funkcjonalność.

Zalety interfejsu w stosunku do klasy abstrakcyjnej:

  • można implementować wiele interfejsów, dziedziczyć można tylko z jednej klasy
  • klasa abstrakcyjna może mieć więcej niż byśmy chcieli przekazać do klienta - np. zarządzanie instancją, konstruktory, atrybuty
  • interfejsu nie można rozszerzyć przez proste dopisanie słówka "public" przed istniejącą metodą
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 12 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2

Proponuję prostą zasadę: interfejs do wszystkiego co wstrzykujemy (ale dzisiaj, a nie co moglibyśmy ew. wstrzykiwać).

Proponuję tego nie robić. Jest jedna implementacja i nie jest to nasze API - no to nie bawimy się w interfejsy.

http://www.adam-bien.com/roller/abien/entry/rethinking_packaging_modularization_interfaces_with

(Pomijając, że proponuję najlepiej niczego nie wstrzykiwać - a tylko zwyczajnie instancjonować - jak ludzie).


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
Zobacz pozostałe 3 komentarze
vpiotr
Przecież tu masz DI przez konstruktor.
jarekr000000
No dokładnie -(sorry - przytrollowałem do frameworków jak zwykle, a możliwe, że nie przeczytałeś mojego komentarza do końca). Bo możliwe, że nie mamy różnego zdania :-). Czepiłem się słówka "wstrzykiwać". Ja polecam nie "wstrzykiwać" tylko przekazać do konstruktora jako parametr.
vpiotr
DI < CDI ~ IoC. Pewnie chodziło Ci o te dwa ostatnie.
Shalom
@jarekr000000 nie myl DI / Wstrzykiwania Zależności z kontenerami IoC które są tylko jedną z możliwych realizacji DI. Przekazywanie parametrów w konstruktorze to też jest DI. Nie ma DI kiedy obiekt sam sobie woła new i tworzy zależności.
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 16 godzin
0
jarekr000000 napisał(a):

(Pomijając, że proponuję najlepiej niczego nie wstrzykiwać - a tylko zwyczajnie instancjonować - jak ludzie).

Czyli masz trzy klasy/interfejsy wymagane przez konstruktor, każda z nich wymaga do stworzenia kolejnych trzech klas/interfejsów, dziergasz te dziewięć new w każdym miejscu, w którym potrzebujesz instancję. A pół roku później robisz refactoring i z pierwszy z parametrów zmieniasz na inny interfejs, ponadto dodajesz czwarty, następnie spędzasz cały dzień na poprawianiu miejsc, w których tworzysz instancję. Brawo! Wykonałeś z wielkim sukcesem masę nikomu niepotrzebnej pracy. A teraz siadasz do aktualizowania testów...


jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 12 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

Jakoś dziwnym trafem jedyne problemy z systemem po refaktoringu i z dopasowaniem testów widzę w systemach grubo korzystających z JavaEE lub Spring - bo się np. nie te co trzeba zapomniane implementacje wstrzykują i po zmianie mocki siesypią.
A tak btw. jak byś się dobrze zastanowił to nie ma zwykle 9 new. ( Czasem mam przez chwilę i to oznaka, że słabo zaprojektowałem system :-) Właśnie jeden taki case poprawiam :-) )
Normalnie nie przekazujesz wszystkich zależności w głąb. (a raczej jedną).


jeden i pół terabajta powinno wystarczyć każdemu
Koziołek
Ale nadal musisz je samodzielnie stworzyć, na wielu poziomach, a następnie całość jakoś ze sobą powiązać.
jarekr000000
Dokładnie tak jak w Springu. Tylko ja widzę gdzie :-)
Koziołek
Czy ja wiem czy takie ręczne wiązanie jest wygodne? Z drugiej strony na pewnym poziomie wymusza to robienie małych modułów.
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Jakoś dziwnym trafem jedyne problemy z systemem po refaktoringu i z dopasowaniem testów widzę w systemach grubo korzystających z JavaEE lub Spring

@jarekr000000 nic w tym dziwnego, bo to są pewnie zwyczajnie duze systemy. W kodzie na 200 linijek w Haskellu tego problemu nie będzie, nie dlatego ze Haskell taki dobry tylko ze złożoność problemu mniejsza. Takie podejście że wstrzykiwanie jest złe jest równie krókowzroczne co podejście new jest złe ;)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 12 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

Tylko widzisz ja np. te "duże Systemy" powoli przerabiam na normalne. I jakoś im się poprawia. (Co prawda wywalanie niepotrzebnego wstrzykiwania to tylko część pracy i raczej nie najważniejsza).


jeden i pół terabajta powinno wystarczyć każdemu
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 16 godzin
0

Co różni "duży system" od "normalnego"? Jak sprawdzasz/mierzysz to, że systemowi się "poprawiło"?
Usuwanie DI pachnie premature optimization. Jeśli IoC jest za ciężkie, to trzeba użyć lżejszego frameworka, a nie poświęcać czas na zmienianie w kilkuset miejscach kodu, po których to zamianach zyska się jeden promil na wydajności. Optymalizuje się tylko wąskie gardła.


jarekr000000
Duży system potrzebuje magii i magika do działania. Jestem magikiem, ale mi się nie chce cały czas korzystać z księgi czarów. I wyżej nie pisałem ani o usuwaniu DI, ani o optymalizacji.
SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0

@jarekr000000:
czyli robisz jakoś tak?:

Kopiuj
class Root{
    Root(){
        this.a = new A( new B( new C( new D())));
    }
}

To chyba lepiej robić new w konstruktorach i dodać setery, w końcu setery to też forma wstrzykiwania zależności ;)

Zobacz pozostałe 10 komentarzy
SP
No taki że nie będziemy mieli tasiemca jak w moim przykładzie, możemy też przetestować klasę A mokując zależność B
Wibowit
A teraz nie możemy przetestować klasy A? przecież mogę zrobić new A(mock(B.class))
SP
Dlatego napisałem w swoim pierwszym poście w tym wątku że pewną zaletą interfejsów jest to, że możemy zrobić moka implementując interfejs (gdyby klasa A brała interfejs "IB" zamiast klasy B) bez używania zewnętrznego frameworka. Gdyby nie brała interfejsu, to można by jeszcze napisać moka dziedzicząc po klasie B (o ile byłoby to możliwe) i nadpisując jej metody. Po klasie B mógłbym sobie dziedziczyć gdyby miała chroniony, bezargumentowy konstruktor, albo gdyby tak jak w Twoim przykładzie konstruktor B nie sprawdzał argumentów- to można wsadzić nulle
Wibowit
Klasy można mockować bez problemu, nie trzeba robić interfejsów. No chyba, że ktoś sobie użyje modyfikatora final na klasie, ale to byłby strzał w stopę z premedytacją.
Wibowit
Konstruktor nie powinien sprawdzać argumentów. Jeśli argumenty wymagają sprawdzenia to trzeba ukryć konstruktor (np modyfikatorem protected), a sprawdzanie argumentów zrobić w metodzie fabrykującej.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.