konto usunięte

Temat: Połączenie z bazą

Karim Agha:
To o czym mówię nie jest też wstrzykiwaniem zależności a "Context Object".

IMHO context objects sa bardziej niebezpieczne niz singleton, wyobraz sobie prosty przyklad gdy kontrakty wywoluja sie nawzajem A->B->C->D. Jezeli chcesz wstrzyknac kontekst dla D to wywolujac A musisz wiedziec o tym czego w swojej implementacji wymaga. Twoj kod wywolujacy nie moze polegac juz na kontrakcie ale na tandemie kontrakt+context, czyli innymi slowy na bezposrednim wywolaniu konkretnej implementacji.

Singleton zostal wymyslony nie po to, zeby przez niego przesylac niejawnie informacje, ale po to, zeby optymalizowac wykorzystanie zasobow. Nie powinno sie korzystac z singletona w sposob inny jak kontrakt, ktory wykonany jest (niemal) identycznie przy klasie jako singleton i jako nie-singleton. W przeciwnym wypadku masz spaghetti, bo Twoj singleton to nic innego jak przestrzen globalna nazw (ktora jest rakiem programu niezaleznie jak jest zaimplementowana)

konto usunięte

Temat: Połączenie z bazą

Sebastian Pienio:
IMHO context objects sa bardziej niebezpieczne niz singleton, wyobraz sobie prosty przyklad gdy kontrakty wywoluja sie nawzajem A->B->C->D.
Zanim nie odpowiem, to nie do końca wiem, co masz na myśli przez "kontrakty". Z kontekstu domyślam się, że pewnie nie chodzi o kontrakty kodu a o instancję obiektów.
Jezeli chcesz wstrzyknac kontekst dla D to wywolujac A musisz wiedziec o tym czego w swojej implementacji wymaga.

Masz na myśli, że przekazuję obiekt kontekstowy do A następnie A wywołuję B z tym obiektem, B tworzy C i tak idzie aż do D?

Bo jeśli tak, to jest to mało bezpieczny sposób na projektowanie API.
wystarczy, że jakikolwiek z tych komponentów wykona:

context = null;

wówczas cały system nam się wykrzaczy bez możliwości nawet kontroli momentu w którym będzie ArgumentNullException.

Prawidłowym sposobem na wykonanie tego jest:

system()
{
Context context = DajMiKontext();
A(context);
// 1
B(context);
// 2
C(context);
// 3
D(context);
}


W takim wypadku, nasz system ma kontrolę i informację o stanie kontekstu po każdej operacji. I teraz możemy w [1-3] sprawdzać czy obiekt kontekstowy dalej spełnia nam warunki niezbędne do tego aby mógł być przekazany dalej.
Wywołanie A->B->C->D też zwiększa zależności pomiędzy klasami.

Twoj kod wywolujacy nie moze polegac juz na kontrakcie ale na tandemie kontrakt+context, czyli innymi slowy na bezposrednim wywolaniu konkretnej implementacji.
Singleton zostal wymyslony nie po to, zeby przez niego przesylac niejawnie informacje, ale po to, zeby optymalizowac wykorzystanie zasobow. Nie powinno sie korzystac z singletona w sposob inny jak kontrakt, ktory wykonany jest (niemal) identycznie przy klasie jako singleton i jako nie-singleton. W przeciwnym wypadku masz spaghetti, bo Twoj singleton to nic innego jak przestrzen globalna nazw (ktora jest rakiem programu niezaleznie jak jest zaimplementowana)

I teraz wyobraź sobie sytuację gdzie mamy singleton i jakiś obiekt w systemie wykona na nim operację która zmieni jego stan na taki który będzie wadliwy. Nie ma mechanizmu kontroli tego stanu, chyba, że w każdym przypadku dostępu do tego singletona każda operacja będzie sama sobie sprawdzała te założenia (wyjątkowo kiepski pomysł).

Przykład:

Mamy system, gdzie mamy obiekt kontekstowy C, dalej mamy kroki w workflow A, B, C, D.

Każde z tych kroków buduję kolejne porcję obiektu kontekstowego, gdzie kolejne kroki zakładają, że ten kontekst je posiada (kiedykolwiek w ASP.NET sprawdzałeś, czy this.Request nie jest nullem? nie - bo założenia projektowe są takie, że jak już dojdzie do pewnego punktu w workflow, to taki wymóg musi być spełniony. W przeciwnym wypadku nawet nie dochodzi do niego.)

W przypadku singletona, zrzucasz odpowiedzialność za pilnowanie workflow na komponenty (powtarzasz się (DRY), to raz, dwa zrzucasz odpowiedzialność której nie powinna być ponoszona na komponenty zewnętrzne).

W przypadku obiektu kontekstowego, system pilnuję tego.

W tym tygodniu zobaczyłem te wpisy:

http://blogs.msdn.com/scottdensmore/archive/2004/05/25...
http://scottdensmore.typepad.com/blog/2009/08/singleto...

Jeszcze innym (ale tu już bardzo subiektywnym) powodem dla którego singletony są be jest taki, że trudniej w nich użyć mojej ulubionego stylu pisania - TDD.

Pozdrawiam

konto usunięte

Temat: Połączenie z bazą

Karim Agha:
Prawidłowym sposobem na wykonanie tego jest:

system()
{
Context context = DajMiKontext();
A(context);
// 1
B(context);
// 2
C(context);
// 3
D(context);
}


W takim wypadku, nasz system ma kontrolę i informację o stanie kontekstu po każdej operacji. I teraz możemy w [1-3] sprawdzać czy obiekt kontekstowy dalej spełnia nam warunki niezbędne do tego aby mógł być przekazany dalej.

No i tutaj jest problem, operacje B, C, D zaleza bezposrednio od operacji A. Context stal sie globalnym namespace'm, a kontrola jego poprawnosci zrzucona zostala na kod wywolujacy (to jest anty-pattern). To bardzo niebezpieczna praktyka.
Singleton zostal wymyslony nie po to, zeby przez niego przesylac niejawnie informacje, ale po to, zeby optymalizowac wykorzystanie zasobow.
I teraz wyobraź sobie sytuację gdzie mamy singleton i jakiś obiekt w systemie wykona na nim operację która zmieni jego stan na taki który będzie wadliwy. Nie ma mechanizmu kontroli tego stanu, chyba, że w każdym przypadku dostępu do tego singletona każda operacja będzie sama sobie sprawdzała te założenia (wyjątkowo kiepski pomysł).

Pisalem (wyzej), ze to nie jest prawidlowe wykorzystanie singletona, ze powinien on udostepniac contract (kontrakt kodu) identyczny niezaleznie od tego, czy jest singletonem czy nie. Swoja droga Twoj przyklad z context'em bedzie mial duzo gorsze skutki w systemie, bo kod wywolywany nie tylko zalezy od stanu obiektu, ale rowniez od poprzednich "budowniczych", a ich zmiana (reimplementacja?) moze miec katastrofalne skutki dla wielu innych komponentow. Jeszcze gorzej, bo nie mozesz przewidziec dokladnie ktore czesci systemu wywoluja C i D i zaleza od wartosci pozostawionych przez A. Hmmm, niezly bigos z tego by wyszedl...
Każde z tych kroków buduję kolejne porcję obiektu kontekstowego, gdzie kolejne kroki zakładają, że ten kontekst je posiada (kiedykolwiek w ASP.NET sprawdzałeś, czy this.Request nie jest nullem? nie - bo założenia projektowe są takie, że jak już dojdzie do pewnego punktu w workflow, to taki wymóg musi być spełniony. W przeciwnym wypadku nawet nie dochodzi do niego.)

To jest koncepcyjny problem, z kontekstu zrobiles globalna przestrzen nazw. To zdecydowanie anty-pattern, a singleton nie ma tu nic do rzeczy.

Tak, sprawdzam zawsze czy Request jest nullem, ale kazdy element jest opakowany w implementacje zalezne od platformy. I tak IContext ma trzy implementacje: dla ASP.NET, WinForms i SilverLight. Ja pytam zawsze tak samo IjBindingContext.Get<IContext>() i dostaje odpowiednia implementacje dla srodowiska. To czy pracuje ona jako singleton czy nie nie ma dla mnie znaczenia dopoki robi to, czego od niej oczekuje. Jak nie robi dorzucam unit testa i poprawiam kod.
Jeszcze innym (ale tu już bardzo subiektywnym) powodem dla którego singletony są be jest taki, że trudniej w nich użyć mojej ulubionego stylu pisania - TDD.

Budowanie kontekstow jest bardzo ciezko testowalne, czesto praktycznie niewykonalne. Powod jest prozaiczny, nie jestes w stanie przewidziec jak A, B i C dokladaja sie do kontekstu. W praktyce musisz testowac mockami, a to jak wiemy najprzyjemniejsze (najszybsze) nie jest.

Budowanie singletona do wykorzystania zasobow mozna przetestowac bardzo latwo zmieniajac sposob dostepnosci obiektu z singleton na nie-singleton (najczesciej to jeden przelacznik w kontenerze IoC).

Niezaleznie od tego jakie praktyki wykorzystujesz IMHO kod wykonujacy powinien zawsze sprawdzic czy jest w stanie wykonac operacje. Powinno sie to robic niezaleznie od tego czy korzystasz z kontraktu singleton czy nie singleton (wogole nie powinno dla Twojego kodu to miec najmniejszego znaczenia). Przyklady:
- wykonanie query: czy mam otwarte polaczenie, czy klasa jest zmapowana, czy musze nalozyc security
- akceptacja klienta: czy przekazany mi obiekt nie jest nullem, czy mam prawo do tego, czy klient istnieje, czy nie jest usuniety, czy nie jest zablokowany itp. itd.

Wtedy masz pewnosc, ze jezeli czesc kodu zawiedzie (i przekaze np. nulla), nie wykrzaczy reszty systemu jakims wyjatkiem, a jedynie odpowie "sorry, nie dalem rady, tu masz info dlaczego" i pokaze komunikat uzytkownikowi. Funkcja nie zadzialala, ale system stoi stabilnie :)

konto usunięte

Temat: Połączenie z bazą

No i tutaj jest problem, operacje B, C, D zaleza bezposrednio od operacji A. Context stal sie globalnym namespace'm, a kontrola jego poprawnosci zrzucona zostala na kod wywolujacy (to jest anty-pattern). To bardzo niebezpieczna praktyka.

Dokładnie tak. W przykładzie który podałeś:
MHO context objects sa bardziej niebezpieczne niz singleton, wyobraz sobie prosty przyklad gdy kontrakty wywoluja sie nawzajem A->B->C->D.

Cały łańcuch B, C, D jest zależny od A.

Ale w przypadku kontekstu, nie jest globalnym namespace, z tego względu, że dostęp do niego mają tylko A, B, C i D (czyli tylko te komponenty, którym przekażę ten obiekt) a E już nie ma dostępu - co czyni go nieglobalnym.

Kontrola poprawności jest zrzucana na obiekt wywołujący, który wywołuję inne elementy systemu. To prawda. To jest IoC, IoC nie jest anty-patternem.
Twoj przyklad z context'em bedzie mial duzo gorsze skutki w systemie, bo kod wywolywany nie tylko zalezy od stanu obiektu, ale rowniez od poprzednich "budowniczych".

Z tym, że po każdym z "budowniczych" jestem w stanie sprawdzić czy dalej jest poprawny i odpowiednio zareagować w przypadku kiedy by nie był poprawny.


/->C \
O -> A -> B -> E
\->D /

O, jest obiektem który budujemy i staramy się opakować w singleton albo kontekst. W przypadku kiedy jest to obiekt kontekstowy i kontrolę nad tym ma system, możemy po wykonaniu B sprawdzić stan obiektu i zastosować strategię awaryjną i np. przekazać go co C a w przypadku kiedy jest poprawny przekazujemy go do D.

W takiej konfiguracji, C i D nie muszą wiedzieć o swoim istnieniu, tak samo jak i reszta obiektów. Więc D nie musi sprawdzać czy O jest poprawny i wysyłać go do C jeśli nie, bo zrobi to za niego system.

Nie możesz zakładać, że osoba pisząca komponent D będzie na tyle sprytna żeby zaimplementować strategię awaryjną. Ta osoba może być nowa w projekcie i nie wiedzieć jeszcze, że jest C, że trzeba tam to przekierować a tak, to tylko zaimplementuję funkcjonalność D (przy podstawowej weryfikacji wartości O) i ma zadanie wykonane.

W przypadku singletona, komponenty musza wiedzieć o całej reszcie systemu i je uwzględniać co niepotrzebnie zwiększa powiązania pomiędzy klasami a dodatkowo i tak każde wywołanie zależy od poprzednich (tego nie przeskoczymy w żadnym modelu, można jedynie nad tym sprytnie zapanować).
To jest koncepcyjny problem, z kontekstu zrobiles globalna przestrzen nazw. To zdecydowanie anty-pattern, a singleton nie ma tu nic do rzeczy.

J/w. Dodatkowo, Singletonowi bliżej do zmiennych globalnych. Nie ma sposobu na dokładną kontrolę tego kto ma do niego dostęp.
Tak, sprawdzam zawsze czy Request jest nullem, ale kazdy element jest opakowany w implementacje zalezne od platformy.
Zobacz: http://msdn.microsoft.com/en-us/library/ms178472.aspx
Sprawdzanie w Page.Load czy Request jest nullem jest bez sensu (nigdy nie będzie) dlatego, że ASP.NET nie dopuści do wykonania tego kroku jeśli te założenie nie jest spełnione. Właśnie o takim czymś mówię, przekazywanie piłeczki dalej do kolejnych kroków.

Budowanie kontekstow jest bardzo ciezko testowalne, czesto praktycznie niewykonalne. Powod jest prozaiczny, nie jestes w stanie przewidziec jak A, B i C dokladaja sie do kontekstu. W praktyce musisz testowac mockami, a to jak wiemy najprzyjemniejsze (najszybsze) nie jest.

Nie ma problemu z Mockami, używam TypeMock do tego i jest to bardzo proste. (Inne frameworki Mocków też ułatwiają mockowanie).
Za to, wiem, że kolejne unit testy nie wpłyną mi na stan obiektu kontekstowego. W przypadku Singletona trudniej nad tym zapanować.

I wręcz przeciwnie, w przypadku obiektu kontekstowego, jestem w stanie tak spreparować obiekt kontekstu w przypadku unit testów aby symulować poprawną kolejność wykonania komponentów. Tym sposobem poprawnie testuję obiekty przez to, że testuję tylko jeden czynnik, zamrażając pozostałe.

Ogólnie dzięki za fajną wymianę zdań :)
Raczej nikt z nas drugiego nie przekona ale przynajmniej będzie co poczytać jak ktoś wejdzie na to forum zastanawiając się jak zbudować system. Będzie miał porównanie dwóch różnych podejść do problemu.

Pozdrawiam



Wyślij zaproszenie do