Moduł w Inversion of Control

0

Moduł, pakiet (ang. module, unit, package) – oddzielny twór, przeważnie w postaci osobnego pliku, zawierający zdefiniowany interfejs, a także implementacje typów wartości, klas, zmiennych, stałych oraz treści procedur i funkcji.

Czy moduł w Javie jest oddzielnym pakietem, czy może modułem może być po prostu jedna klasa.

Przerabiam teraz inversion of centrol gdzie jest powiedziane ze moduły wysokopoziomowe nie powinny zależeć od niskopoziomowych, czyli wysokopoziomowe to te od od których zaczyna się przepływ programu (?

4

Pojęcia w IT są w cholerę nieprecyzyjne. W ramach języka programowania są co prawda często bardzo precyzyjne, ale na poziomie między jezykowym są już totalnie nie precyzyjne

Sebastian Zakrzewski napisał(a):

Moduł, pakiet (ang. module, unit, package) – oddzielny twór, przeważnie w postaci osobnego pliku, zawierający zdefiniowany interfejs, a także implementacje typów wartości, klas, zmiennych, stałych oraz treści procedur i funkcji.

Przy tej definicji to faktycznie Moduł to jest mniej więcej klasa w Javie. Ale w Javie w jednym pliku może być zdefiniowanych wiele klas. A w takiej Scali czy w Kotlinie to już jest w ogóle jazda bez trzymanki. Dla odmiany w Haskellu jest porządek i tam jeden plik z kodem to jeden moduł

W Javie słowo moduł ma inne znaczenie i to czesto wynik kompilacji projektu (Dla odmiany w Haskellu skompilowany projekt to pakiet, więc jest wszystko na odwrót :D ).
Multi projekty zawierające podprojekty moga generować wiele modułów. Wtedy jest hierarchia:

  • Moduł ((pod)projekt) zawiera pakiety
  • Pakiet zawiera klasy (i inne pakiety)
  • Klasa zawiera metody funkcje itd (oraz oczywiście inne klasy :D )
1

w Node.js śmiesznie, bo katalog node_modules nie trzyma tak naprawdę modułów, tylko katalog z pakietami (packages) i dopiero te pakiety zawierają pliki-moduły.

5

Przerabiam teraz inversion of centrol gdzie jest powiedziane ze moduły wysokopoziomowe nie powinny zależeć od niskopoziomowych, czyli wysokopoziomowe to te od od których zaczyna się przepływ programu?

Biorąc pod uwagę, że programy na ogół nie dzielą się na dwie warstwy (tę "niskopoziomową" oraz tę "wysokopoziomową"), a raczej są fraktalami, moją osobistą sugestią byłoby postrzeganie IoC/DI jako "im głębiej w las, tym bardziej niskopoziomowo".

Na przykład mając taką hierarchię:

class Scraper {
  AllegroScraper ...;
  OlxScraper ...;
}

class AllegroScraper {
  AllegroHttpClient ...;
  AllegroHtmlParser ...;
}

class OlxScraper {
  OlxHttpClient ...;
  OlxHtmlParser ...;
}

class AllegroHttpClient {
  HttpClient ...;
}

class OlxHttpClient {
  HttpClient ...;
}

class AllegroHtmlParser {
  /* ... */
}

class OlxHtmlParser {
  /* ... */
}

class HttpClient {
  /* ... */
}

... nie możemy jednoznacznie stwierdzić co jest "niskopoziomowe" a co "wysokopoziomowe" - możemy za to zauważyć, że Scraper jest "bardziej wysokopoziomowy" od OlxScraper (bo koordynuje jego działaniem), ale jednocześnie AllegroScraper jest "bardziej wysokopoziomowe" od HttpClient.

Mimo to, choć nie możemy jednoznacznie stwierdzić co jest "wysoko" a co "nisko", to możemy powiedzieć, że IoC (DI?) jest - uhm - "spełnione", ponieważ im głębiej w las (im głębiej w zależności Scraper), tym wszystko staje się "bardziej niskopoziomowe".

I, ten tego, tym sugerowałbym się kierować :-)

2

Próbujesz mapować konkretne rzeczy na abstrakcyjne pojęcie. Przytoczyłeś definicję modułu, która jest z grubsza OK, jedyne czego bym się czepiał, to tego "osobnego pliku".

Moduł, to jakiś kawałek twojej aplikacji, dla którego jesteś w stanie zdefiniować zewnętrzny interface i zamknąć w puszce do której nie ma potrzeby zaglądać, a nawet jak ktoś tam coś pozmienia, to wciąż nie jest to widoczne z zewnątrz, bo opakowanie pozostaje takie samo. Nikt nie powiedział, że moduł nie może składać się z mniejszych modułów. To może być klasa, może to być pakiet (trochę bieda, bo w Javie pakiety są naprawdę słabe).
Często jest tak, że na zewnątrz "modułu" widzisz pojedynczą klasę, a wewnątrz masz ich sporo, ale wie o nich jedynie ta klasa zewnętrzna.

3
Sebastian Zakrzewski napisał(a):

Przerabiam teraz inversion of centrol gdzie jest powiedziane ze moduły wysokopoziomowe nie powinny zależeć od niskopoziomowych, czyli wysokopoziomowe to te od od których zaczyna się przepływ programu

Jeśli próbujesz zrozumieć czym jest "Inversion of control", to raczej nie myśl o modułach. Skupiłeś się nie na tej rzeczy co potrzeba.

Potrzebujesz pewnej bazy w wiedz i doświadczeniu, żeby zrozumieć o co chodzi w IoC, najprościej byłoby to pokazać na jakimś przykładzie.

Ale w prostych słowach, najczęściej IoC ogarnia się polimorfizmem, w taki sposób że niektóre kawałki kodu (funkcje, klasy, moduły) mogą się zmieniać, tak że nie powodują one zmian w innych kawałkach kodu (innych klasach i modułach). Ma to wiele zalet. Rysuję to pewną "barierę" albo "rozdziałkę" między fragmentami kodu, co w pewien sposób oddziela te kawałki od siebie. Najczęściej taką rozdziałkę IoC się wstawia właśnie pomiędzy niskopoziomowe i wysoko poziomowe moduły, ale to nie jest zasada. Możesz też użyć IoC pomiędzy dwoma niskopoziomowymi albo dwoma wysokopoziomowymi modułami., możesz też dodać moduły pośredniczące między nimi.

Ten wierszyk moduły wysokopoziomowe nie powinny zależeć od niskopoziomowych to jest bardzo często niepoprawnie cytowany buzzword.

0

Dzięki rzuciliście mi trochę światła bo nie mogłem przez to spać w nocy :p

0

@KamilAdam:

No i jeszcze od Javy 9 mamy "moduły Javy" ... żebym widział wiwatujące z tego powodu tłumy programistów, to nie powiem ...
Wiem, ze długo różne duże projekty na to cierpiały ...

0

Czy mniej więcej dobrze zrozumiałem, rozrysowalem to na kartce, przepraszam za brak umiejętności artystycznych

1

@Sebastian Zakrzewski: Sprowadzasz IoC do "wstaw interface", a to czasami pożyteczny detal. Oczywiście powinieneś używać maksymalnie wysokiej abstrakcji funkcjonalności, której potrzebujesz i często jest to jakiś interface, ale nie to jest moim zdaniem "sednem" koncepcji IoC.
Lista jest kiepskim przykładem, bo akurat w tym przypadku stosowanie IoC ma rzadko sens, ale chwilowo nic lepszego mi nie przychodzi do głowy:

public class MyClass{
  private List<String> myList = new ArrayList();
}

Masz tutaj bezpośrednią zależność, w której MyClass zależy od ArrayList. Nie jesteś w stanie podmienić implementacji na np. LinkedList. Czyli moduł wysokiego poziomu (MyClass), zależy od czegoś niskiego poziomu (ArrayList) i interface niczego tutaj nie zmienia.

public class MyClass{
  private List<String> myList;
  MyClass(List<String> myList){
    this.myList = myList;
  }
}

Tutaj, dla odmiany, możesz zdecydować jakiej konkretnej implementacji interface'u List chcesz użyć, podczas wywoływania new MyClass(), bo zależy ona od jakiejś tam funkcjonalności opisanej interface'm, a nie konkretnej implementacji.

Tylko serio, nie warto dopisywać interface'u w każdym możliwym miejscu i wstrzykiwać z zewnątrz każdej możliwej klasy. Gdybyś wrzucał jakieś DAO do kontrolera, to pewnie warto skorzystać z takiego mechanizmu, natomiast w przypadku kolekcji, rzadko kiedy to ma sens.

2
Sebastian Zakrzewski napisał(a):

Czy mniej więcej dobrze zrozumiałem, rozrysowalem to na kartce, przepraszam za brak umiejętności artystycznych

Nie do końca.

W Inversion of Control, chodzi o to że kontrola przepływu idzie w jedną stronę, podczas gdy zależności w kodzie źródłowym idą w drugą. Jeśli dodasz polimorfizm w odpowiednim miejscu, to kontrola przepływu już nie jest w tą stronę która wynikałaby z zależności kodu - stąd odwrócenie kontroli.

W "prosto" napisanej aplikacji bez abstrakcji i polimorfizmu, flow of control oraz source code dependency idą w jedną stronę. To jest taki zwykły normalny kod:
screenshot-20220809174256.png

Ale używając polimorfizmu, można zrobić takie coś:
screenshot-20220809174406.png

I to jest Inversion of Control właśnie.

Żeby to ująć jeszcze prościej:

  • W "normalnie" napisanej aplikacji: jeśli chcesz czegoś użyć, musisz to "zaimportować" - zmiany w tym czymś powodują zmiany u Ciebie.
  • W miejscu z odwróceniem kontroli: jeśli chcesz czegoś użyć, to nie musisz tego importować - to coś musi zaimportować Ciebie - zmiany w tym czymś nie powodują zmian u Ciebie, bo nawet tego nie importujesz.
1

@Riddle:

W Inversion of Control chodzi po prostu o to, żeby użyć czegoś, w takim sposób żeby nie mieć zależności na to coś w swoim kodzie źródłowym

No i to jest właściwa definicja.

@Sebastian Zakrzewski:
Uciekasz myślami w jakieś pierdoły, klasy, interface'y, pakiety i doszukujesz się 5-ego dna w rzeczy banalnej.
Jak sobie w swojej klasie (metodzie, pakiecie whatever..) napiszę:
private final MyRepository repository = new MyRepository();

To ten mój "moduł" staje się wprost zależny od konkretej implementacji. (dla czepialskich - tak da się to obejść, ale nie zaciemniajmy).

Jeżeli zrobię sobie to tak:
private final IRepository repository = new MyRepository();
to niby mam interface, ale nadal new powoduje, że mam sztywne połączenie do konkretnej implementacji tego interface'u

Mogę zrobić tak:

public class MyClass{
  private final MyRepository repository;
  public MyClass(MyRepository repository){
    this.repository = repository;
  }
}

public class MyController{
  public void doSomething(){
    var repository = new MyRepository();
    var myClass = new MyClass(repository)
  }
}

To już jest lepiej niż na początku, bo mogę w tym miejscu wstrzyknąć np. klasę dziedziczącą po MyRepository, co powoduje, że mam mniej powodów do zmiany dla MyClass, ale wciąż moje możliwości wpłynięcia na to z czego poskładana jest moja aplikacja są ograniczone.
Załóżmy, że wpada jakiś pomysł, że zamiast MySQL mamy użyć MsSQL - dalej jestem w ciemnej d... bo MyClass zbyt restrykcyjnie określa wymagania co do obiektów, które przyjmuje jako swoje zależności. Dlatego robimy tak:

public class MyClass{
  private final IRepository repository;
  public MyClass(IRepository repository){
    this.repository = repository;
  }
}

public class MyController{
  public void doSomething(){
    var repository = new MsRepository();
    var myClass = new MyClass(repository)
  }
}

Dzięki temu, ani MyClass nie jest zależne od Myrepository, Ta implementacja może nawet nie istnieć w momencie kiedy tworzysz MyClass i możesz się nigdy nie dowiedzieć, że powstała, bo zrobi ją ktoś kiedyś, jak już będziesz pracować winnej firmie.

Użycie interface nie jest warunkiem niezbędnym do tego, żeby mieć "odwrócone sterowanie". Jest to po prostu najpopularniejsze, ale wciąż jedynie jedno z możliwych narzędzi, które można wykorzystać do uzuskania efektu IoC.

W tej zasadzie (i w wielu innych), chodzi o bardzo prostą rzecz. W programowaniu obiektowym należy tworzyć wiele małych (S) bytów, które mogą być łatwo rozszerzane, ale nie ma powodów do ich zmiany (O), mogą być wymieniane, jeżeli tylko spełniają jasno zdefiniowany kontrakt (L), który określa to co naprawdę niezbędne (I), a składanie tych klocków przekazujemy poza nie same (D)

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