sobota, 2 lutego 2013

Cztery smaki odwracania (i utraty) kontroli: Dependency Injection, Events, Aspect Oriented Programming, Framework

W styczniowym wydaniu programistamag.pl ukazał się kolejny mój artykuł pt.: Cztery smaki odwracania (i utraty) kontroli: Dependency Injection, Events, Aspect Oriented Programming, Framework.

Tekst jest syntezą wieloletnich doświadczeń i przemyśleń na temat Inverion of Control. Kolejne techniki coraz to silniejszego odwracania kontroli (powiązanego z jej utratą) zostały opisane wg struktury:

  • problem - jaki problem chcemy rozwiązać, po co w ogóle odwracamy kontrolę w ten sposób
  • idea - ogólna idea zawarta w jednym zdaniu, która pozwoli uchwycić esencję
  • motywacja - kiedy mogę zacząć zastanawiać się nad wprowadzeniem tej techniki
  • zastosowanie - jakie są dodatkowe zastosowania (poza rozwiązaniem problemu) ew benefity uboczne/wyższego rzędu
  • techniki - jakimi technikami implementacyjnymi mogę osiągnąć odwrócenie
  • kiedy nie stosować - najważniejsze: gdzie jest granica stosowalności, na co uważać aby nie skończyć z syndromem "nakładania gaci przez głowę"
Artykuł do pobrania (jak zwykle całkowicie za darmo oraz bez rejestracji:) tutaj: http://bottega.com.pl/artykuly-i-prezentacje#receptury



Artykuł jest jednocześnie pierwszym z nowej serii: Receptury projektowe – niezbędnik początkującego architekta.

Intencją serii „Receptury projektowe” jest dostarczenie usystematyzowanej wiedzy bazowej początkującym projektantom i architektom. Przez projektanta lub architekta rozumiem tutaj rolę pełnioną w projekcie. Rolę taką może grać np. starszy programista lub lider techniczny, gdyż bycie architektem to raczej stan umysłu niż formalne stanowisko w organizacji.

Wychodzę z założenia, że jedną z najważniejszych cech projektanta/architekta jest umiejętność klasyfikowania problemów oraz dobieranie klasy rozwiązania do klasy problemu. Dlatego artykuły będą skupiały się na rzetelnej wiedzy bazowej i sprawdzonych recepturach pozwalających na radzenia sobie z wyzwaniami niezależnymi od konkretnej technologii, wraz z pełną świadomością konsekwencji płynących z podejmowanych decyzji projektowych.


Seria z kolei jest początkiem nowego działu w programistamag.pl: Laboratorium Bottega. W dziale tym będziemy publikować podejścia, rozwiązania i techniki, które stosujemy na co dzień w praktyce projektowej, podczas warsztatów i szkoleń oraz coachingu.

//==================================

Artykuł powstał niejako "na zamówienie" jednego z czytelników bloga. Temat był po prostu zbyt obszerny jak na posta.

Jeżeli ktoś chciałby "zamówić" jak temat to proszę pisać śmiało w komentarzach lub na priv: slawomir.sobotka w domenie bottega.com.pl

19 komentarzy:

Paweł Kaczor pisze...

Brawo! Rzadko można przeczytać tego typu syntetyczne opracowania.

Jedna uwaga do akapitu Kiedy nie stosować DI:
"Czasem zależność pomiędzy obiektami jest naturalna i wynika z modelu,
wówczas nie potrzebujemy ich wstrzykiwać."
Jeśli decydujemy się użyć kontenera DI, trudno o częściowe jego stosowanie. Jeśli nie chcemy wstrzykiwać określonego obiektu, obiekt ten również nie może korzystać z wstrzykiwania zależności.

Sławek Sobótka pisze...

Dzięki, temat "dojrzewał" kilka lat:)

Akapit jest tylko skrótem myślowym i płowieniem go poprzeć przykładem. Chodziło mi o przypadek, gdzie obiekt zarządzany (nazwijmy go "główny") tworzy w sobie swe "naturalne" zależności (których nie będziemy chcieć nigdy wymieniać dynamicznie). Jeżeli te zależności po potrzebują czegoś głębiej, to obiekt "główny" powinien im je przekazać (np przez konstruktor).

Michał Bartyzel pisze...

@Sławek A możesz podać przykład?

Sławek Sobótka pisze...

Przykładowo: jest sobie monitor (np. LCD), który wewnątrz składa się z matrycy i zasilacza. Z jakiegoś powodu chcę móc wstrzykiwać do monitora zasilacze. Ale umówmy się, że matryca jest wyciągnięta do osobnej klasy z powodów estetycznych.

Tak więc wstrzykuję zasilacz do monitora a już wewnętrzną sprawą monitora (jego *aktualnej* implementacji) jest to, że on sobie ten zasilacz przekaże jeszcze do swojej wewnętrznej matrycy (o której istnieniu być może nawet nikt na zewnątrz nie wie).

Irek Matysiewicz pisze...

Eventy są ogólniejsze od AOP - nie zrobisz "onClick" używając AOP, bo nie ma metody "kliknij" w systemie - to jest robione przez użytkownika i przechwytywane przez jądro za pomocą przerwań i dopiero wtedy interpretowane: skoro pochodzi to przerwanie od przycisku myszy to było kliknięcie. Z kolei można sobie wyobrazić API refleksji w stylu: metoda.onInvoke(...) - czyli to co robi AOP.
Z kolei dependency injection (czy Strategia) to nic innego jak event, ale z możliwością dodania tylko jednego listenera (poprzez "wstrzyknięcie").
AOP jest najsłabsze. Kolejność powinna być raczej: AOP, DI, eventy, a nie DI, eventy, AOP.

A monady to tylko sposób na wprowadzenie średnika (najpierw zrób to, a potem tamto) w "czystych" językach funkcyjnych: zamiast pisać potemTamto(najpierwTo()) można napisać najpierwTo(); potemTamto() i jest to potem przerabiane do czegoś w stylu potemTamto(najpierwTo()). Z punktu programisty Java/C#, monady nie wprowadzają nic nowego bo Java średnika ma :-)

Paweł Kaczor pisze...

LOL :)

Sławek Sobótka pisze...

@Irek:

Wygląda na to, że rozumiałeś moją kategoryzację odwracania kontroli "od tyłu".

Ja opisałem ją z punktu widzenia kodu, który traci kontrolę: najpierw nie kontroluje typu współpracownika, później ilości, kolejności i czasu działa współpracowników a później po prostu już nic o nich nie wie.

Ty natomiast patrzysz z punktu widzenia kodu współpracownika, gdzie faktycznie może on coraz mniej.

Czyli utrata kontroli jest symetryczna: im więcej kontroli tracę po jednej stronie, tym więcej jej ma po drugiej (i vice versa).

Ale w sumie to ciekawa obserwacja.

Natomiast co do tego, że Strategy jest Observerem...
Na pewnym poziomie ogólności większość wzorców to będą funktory. Ale jeżeli uogólnimy za bardzo spojrzenie, to stracimy kontekst zastosowania wzorca. Bo tak na prawdę wszystkie patterny można "wyprowadzić" sobie z 4 zasad OO - można ale we wzorcach chodzi właśnie o szczegółowy kontekst, aby postrzegać je jako building blocks. Bo inaczej dojdziemy do Maszyny Turinga:P

Irek Matysiewicz pisze...

No właściwie tutaj nie ma co za długo się sprzeczać, bo pojęcia jak aspekt, event, DI, observer nie zostały matematycznie zdefiniowane, a bez tego każdy może je interpretować trochę inaczej.

Sławek Sobótka pisze...

Tego by jeszcze brakowało aby wzorce projektowe i architektoniczne przenieść do algebraicznej abstrakcji z zbudować nad nimi ciało:PPP

Z fajnej zabawy w "klocki" zrobiłaby się z tego dyscyplina dla odrealnionych indywiduów;)

O ile teraz brakuje programistów, to wówczas już nikt nie chciałby bawić się w tego typu smutne historie:P

Irek Matysiewicz pisze...

Czy takie smutne? Do formalnego definiowania pojęć w programowaniu nie trzeba algebry (nie każdy ukończył matematykę czy informatykę), wystarczą interfejsy Javy (a to chyba każdy programista zna) czy UML (zresztą nawet sam UML został zmodelowany za pomocą UML).
Każda klasy Javy jest jednocześnie strukturą algebraiczną, np.
interface Field {
T plus(T x, T y);
T multiply(T x, T y);
T getZero();
T getOne();
}
plus opis zachowania w postaci programowania kontraktowego czy unit testów może reprezentować ciało w matematyce: (T, +, *, 0, 1). Podobnie z każdą inną strukturą.
Klasy z java.lang.reflect opisują dokładnie czym jest klasa, pole, metoda. To że w Javie piszemy Typ metoda(parametry ...) { ... }, class Nazwa { ... } czy własne DSL-e to tylko syntactic sugar dla kodu typu: metoda = new Method(); metoda.addParameter(...); metoda.addInsn(...).
Tu wystarczyłoby zrobić klasy "Aspect", "Injection", "Event" i każdy by wiedział co jest co. Np.: interface Injected {
Strategy getStrategy();
void setStrategy(Strategy)
}
W kodzie to by było pisane jako syntactic sugar: @Inject Typ pole;
A eventy:
interface Event extends Injected {
void addObserver(Observer);
void removeObserver(Observer);
}
Czy zdajesz sobie sprawę czy nie, pisząc klasy tak naprawdę piszesz struktury algebraiczne.

A czy w Polsce brakuje programistów?
Ja się nawet trochę boję że za kilka lat będzie w Polsce nas za dużo. Uczelnie produkują coraz to nowych, a duże firmy z USA czy Europy zachodniej coraz częściej przenoszą dewelopment do Indii czy gdzie indziej do Azji, a w Polsce ciekawych, rodzimych firm IT jest niewiele. Niby jest program UE "innowacyjna gospodarka", ale on chyba częściej jest używany do wyciągania pieniędzy a nie do tworzenia ciekawych projektów czy firm.

Irek Matysiewicz pisze...

Jeszcze raz bo Blogger zjadł nawiasy trójkątne:

Czy takie smutne? Do formalnego definiowania pojęć w programowaniu nie trzeba algebry (nie każdy ukończył matematykę czy informatykę), wystarczą interfejsy Javy (a to chyba każdy programista zna) czy UML (zresztą nawet sam UML został zmodelowany za pomocą UML).
Każda klasy Javy jest jednocześnie strukturą algebraiczną, np.
interface Field[T] {
T plus(T x, T y);
T multiply(T x, T y);
T getZero();
T getOne();
}
plus opis zachowania w postaci programowania kontraktowego czy unit testów może reprezentować ciało w matematyce: (T, +, *, 0, 1). Podobnie z każdą inną strukturą.
Klasy z java.lang.reflect opisują dokładnie czym jest klasa, pole, metoda. To że w Javie piszemy Typ metoda(parametry ...) { ... }, class Nazwa { ... } czy własne DSL-e to tylko syntactic sugar dla kodu typu: metoda = new Method(); metoda.addParameter(...); metoda.addInsn(...).
Tu wystarczyłoby zrobić klasy "Aspect", "Injection", "Event" i każdy by wiedział co jest co. Np.: interface Injected[Strategy] {
Strategy getStrategy();
void setStrategy(Strategy)
}
W kodzie to by było pisane jako syntactic sugar: @Inject Typ pole;
A eventy:
interface Event[Observer] extends Injected[Observer] {
void addObserver(Observer);
void removeObserver(Observer);
}
Czy zdajesz sobie sprawę czy nie, pisząc klasy tak naprawdę piszesz struktury algebraiczne.

A czy w Polsce brakuje programistów?
Ja się nawet trochę boję że za kilka lat będzie w Polsce nas za dużo. Uczelnie produkują coraz to nowych, a duże firmy z USA czy Europy zachodniej coraz częściej przenoszą dewelopment do Indii czy gdzie indziej do Azji, a w Polsce ciekawych, rodzimych firm IT jest niewiele. Niby jest program UE "innowacyjna gospodarka", ale on chyba częściej jest używany do wyciągania pieniędzy a nie do tworzenia ciekawych projektów czy firm.

Andrzej Ludwikowski pisze...

Jak dla mnie to bardzo smutne... A Wielka Smuta to by była gdyby ktoś matematycznie zdefiniował wspomniane aspekty, eventy i DI. Jak wolę świadomie nie być świadomy modeli matematycznych, które to opisują, bo póki co nie widzę z tego wielkich korzyści.

Irek Matysiewicz pisze...

Czytaj uważnie co napisałem: pisząc klasy/interfejsy w Javie też tworzysz model czegoś tam, tyle że nie w języku matematyki (struktury, funkcje, ...), tylko w języku programowania.
Kod Javowy, UML czy język matematyki służą temu samemu: do modelowania rzeczywistości, kod Javowy możesz przerobić na język matematyki i na odwrót.
Jak uczyli cię w szkole mnożyć pisemnie, rozwiązywać równania kwadratowe czy liczyć pochodną to co to jest - to są algorytmy, tylko zapisane w języku matematyki, ale zawsze możesz to przepisać na Javę.

Klasy z java.lang.reflect mówią dokładnie czym jest klasa czy metoda (podobnie jak metamodel UML mówi czym są poszczególne elementy w UML). Wystarczyłoby dodać taką "metamodelową" klasę Event czy Injection i byłoby wszystko jasne. Nie trzeba tu używać języka matematyki, więc nawet programiści z Indii by to zrozumieli :-)

Sławek Sobótka pisze...

Irek, hmm od czego by tu zacząć...
Akurat od jakiegoś czasu mocno interesuję się tym w jaki sposób działa umysł (i na przykład w szczególności jak modeluje on problemy).

Jest to temat wielowymiarowy, więc upraszczając i skracając: nie masz racji. Rozwijając: różnie z tym bywa u różnych ludzi, w różnych momentach i w różnych sytuacjach:)

Konkretnie: piszesz, że: "Czy zdajesz sobie sprawę czy nie, pisząc klasy tak naprawdę piszesz struktury algebraiczne."

Nie wiem jakie Ty masz wewnętrzne modele problemów gdy je programujesz, ale ja na przykład nie "mam w głowie" (upraszczając) struktur algebraicznych. Tak samo jak pisząc niniejszy tekst nie mam w głowie części mowy, zdania, zasad gramatyki. Tak samo jak biegając nie mam w głowie równań opisujących ruch poszczególnych kości.
Pewnie domyślasz się, że mam tutaj na myśli dosłownie NIE MAM - jestem PEWIEN, że nie są to nieuświadomione procesy mentalne. Przykładowo mogę nie znać algebry ani gramatyki a mimo to budować złożone architektury lub wielokrotnie złożone zdania.

Owszem można niejako "równolegle" formalizować wzorce architektoniczne czy język polski do algebry czy gramatyki. Czyli przenieść naturalne ("operacyjne") reprezentacje mentalne do innej abstrakcji - np algebry. I być może część ludzi rzeczywiście posługuje się takimi symbolicznymi, linearnymi modelami - w tak zwany (w uproszczeniu) lewopółkulowy sposób.

Ale z moich obserwacji zdecydowana większość programistów (praktyków) używa modeli wizualnych, kinestetyczny, audytywnych. Najlepsi projektanci i programiści jakich spotkałem posiłkują się wszystkimi tymi modeli wywodzącymi się "z ciała" (np: strategia wchodzi od góry, całość ma eleganckie proporcje i nie zgrzyta).

Z tego co się orientuję, to wybitne umysły matematyczne również posiłkują się tymi "cielesnymi" procesorami: przenoszą symbolikę do innej reprezentacji (np wizualnej) tak dokonują odkrycia a następnie powracają do symboliki na potrzeby komunikacji z innymi ekspertami:)

Irek Matysiewicz pisze...

Jeju ile mam powtarzać: struktura algebraiczna to dane + zachowania (np. (T - dane, +, * - zachowania, "0", "1" - dane)), klasa to też dane (pola albo getery+setery) + zachowania (metody). Odpowiednikiem matematycznych aksjomatów i twierdzeń w Javie są kontrakty (np z użyciem Google cofoja) i testy.
Różnica wynika tylko ze sposobu zapisu (Java vs język matematyki). Efekt końcowy jest ten sam (tylko w innym języku). Ja też programując nie mam w głowie struktur algebraicznych tylko klasy i metody, ale to to samo.
Nie rozumiem dlaczego nie dostrzegacie analogii między matematyką a programowaniem.

Sławek Sobótka pisze...

1. Piszesz to po raz pierwszy
2. Nie wiem dlaczego nie rozumiesz dlaczego nie dostrzegamy - czy chciałbyś o tym porozmawiać?

ale tak na poważnie:

dostrzegać może i dostrzegamy, ale pytanie po co?

Spójrz na to w ten sposób: masz "graciarnię", w której znajduje się się nieskończenie wiele różnego rodzaju aparatów matematycznych. Jak dobrze tam poszperasz, to znajdziesz taki aparat, który przy dobraniu odpowiednich parametrów będzie wizualizowalny tak, że jego wizualizacje będą niemal identyczne np z orbitą Ziemi. Czy to znaczy, że ziemia co chwilę "oblicza sobie tym aparatem" gdzie powinna być? Wydaje się, że raczej ziemia znajduje się tam, gdzie wynika to z lokalnych reguł "mechaniki przestrzeni" a nam jedynie udało się w graciarni znaleźć coś co przypadkiem nadaje się do pewnych zastosowań (np. przewidzenie zderzenia z kometą).

Na pewno w tej "graciarni" znajdę coś co mogę sobie "przypasować" do AOP czy Eventów, ale cóż z tego, skoro np. moja osobista cieć neuronowa aproksymuje dobór techniki odwracania kontroli np skurczami mięśni ciała posiłkując się korą wzrokową. Efekt wydaje się być zadowalający:)

Irek Matysiewicz pisze...

Dla człowieka mogą być jakieś nieścisłości, bo człowiek znajdzie rozwiązanie nieznanego wcześniej problemu na bieżąco (np. jedziesz samochodem a przed tobą wali się drzewo - przyspieszasz aby zdążyć albo hamujesz; coś w programie wolno działa - próbujesz znaleźć albo wymyślić szybszy algorytm).
A ponieważ komputery są kompletnie głupie, w programowaniu (czy też matematyce) trzeba opisać dokładnie jak najwięcej typowych i "brzegowych" sytuacji, a najlepszy jest nie sam Javadoc i ładne nazwy metod, ale też "ścisły" opis (kontrakty, testy, gramatyki języka) - każdy programista zrozumie je tak samo, a komputer może znaleźć kontraprzykłady że dana klasa jest źle zaimplementowana (albo test jest zły).
Wiem że są projekty w których nie pisze się nawet testów, a prawie nikt nie używa programowania kontraktowego, ale to jest źle.

Każdy wie czym jest w Javie klasa, pole czy metoda, ale ty nie napisałeś czy przez AOP masz na myśli java.lang.reflect.Proxy, czy Spring AOP, AspectJ, czy Bytemana, czy nawet metaprogramowanie, łącznie z patchowaniem innych procesów a nawet kernela. Czy przez dependency injection masz na myśli "zwykłe" wstrzykiwanie przez konstruktor/pole/seter czy też wstrzykiwanie metod znane ze Springa. Czy eventy to u ciebie tylko Javowe listenery (wzorzec Observer) czy też coś innego, np. polling, przerwania, object.wait()/semafory/locki (czekanie na eventa innego wątku albo procesu), HTTP server push, JMS, extension pointy, ...
Każdy z tych rodzajów AOP/wstrzykiwania/eventów potrafi co innego i mniej lub bardziej odwraca kontrolę, i dopiero po dokładnym napisaniu o co Ci chodziło można mówić co mniej a co bardziej odwraca kontrolę. Np. AspectJ bardziej odwraca kontrolę niż dependency injection tylko dla klas które możesz modyfikować (np. z AspectJ uruchomionego jako agent zwykle nie możesz modyfikować klas z rt.jar), dla kodu niemodyfikowalnego (zwykle rt.jar, kod w JNI, inne procesy, jądro) AspectJ jest słabszy niż eventy, bo tu niewiele AspectJ może zrobić.

Sławek Sobótka pisze...

Odnośnie pierwszego akapitu: no więc właśnie zaczynami mówić wspólnym językiem:)

Linearne symboliczne procesy myślowe myślowe są użyteczne do przeprowadzenia dowodu. Natomiast "olśnienia" zachodzą w reprezentacjach nielinearnych i niesymbolicznych. Podsumowanie najnowszych odkryć: http://www.amazon.co.uk/Master-His-Emissary-Divided-Western/dp/0300188374

Technicznie (na poziomie neuronów) działa to prawdopodobnie dzięki "rzadkim reprezentacjom" i hierarchicznej, samoorganizującej się pamięci. Jak twierdzi Jeff hawkins mózg nie wykonuje obliczeń, jest "jedynie" hierarchiczną samoorganizującą się pamięcią z rzadkimi reprezentacjami. https://www.google.pl/search?client=opera&q=Jeff+Hawkins+sparse

Co do drugiego akapitu to wreszcie powoli "odkrywasz" mi wewnętrzne sposoby myślenia o zagadnieniach od których rozpoczęła się dyskusja. Ok, u mnie wygląda to inaczej, zaczynając myśleć o architekturze nie zastawiam się np nad implementacją AOP. Ja osobiście mam zbyt małą "przestrzeń roboczą" aby myśleć zarówno o koncepcyjnym zastosowaniu AOP (sensowności jego wprowadzenia, konsekwencjach itd) jak i jego implementacji na poziomie bytecode. Ale jeżeli ktoś potrafi operować równolegle na różnych poziomach szczegółowości to pewnie znalazł już sobie miejsce gdzie będę mu słoooono płacić za taki skill:)

Irek Matysiewicz pisze...

Prawda: jakiś matematyk powiedział kiedyś że najlepsze pomysły powstają "intuicyjnie", a formalizm (matematyczny czy jakikolwiek) służy tylko by udowodnić że to działa i że ma takie a takie własności.

A skill (lub jego brak?) nijak nie ma się do płacy, ja na umowę o pracę jak dotąd najwięcej zarabiałem tylko ok. 4000 na rękę - to jest bardzo daleko do warszawskiej średniej na stanowiskach programistycznych.