Wednesday, June 25, 2008

Wzorzec Job Security

Prowadząc szkolenia z refaktoringu, coraz częściej natykam się na pewien specyficzny schemat zachowania. Schemat, który napawa mnie przerażeniem i kładzie cień na moich programistycznych ideałach. A było to tak...

Wykładałem pracowicie cel refaktoringu, konkretne techniki właściwe dla poszczególnych warstw i technologii i...nic jak grochem o ścianę - wszyscy uparcie twierdzili, że ich manager w życiu nie zgodzi się na poświęcenie choćby chwili na refaktoring. Przeszedłem więc do broni masowego rażenia - korzyści płynące ze stosowania refaktoringu:

  1. Kod jest łatwiejszy w czytaniu

  2. Łatwiej jest dokładać nowe funkcjonalności

  3. Wdrożenie nowej osoby do projektu zajmuje mniej czasu

  4. Testowanie kodu jest łatwiejsze

  5. Odnajdywanie błędów trwa krócej

  6. Rozbudowywanie projektu zajmuje mniej czasu


A zatem, argumentowałem, potencjalnie zyskujemy najcenniejszy zasób: czas. A czas to, jak wiadomo, pieniądz! Zatem manager musi zgodzić się na rozprzestrzenianie praktyki refaktoringu...

Słuchaj, Michał - powiedział jeden z uczestników szkolenia - jeśli napiszę w czasie krótszym niż mój manager to oszacował to dostanę kolejny projekt. Dodatkowo na projekt zostanie przydzielone mniej czasu, bo już pokazałem, że potrafię pracować efektywniej. I tak z każdym kolejnym projektem mój manager wyciśnie ze mnie wszystkie soki. Jeśli dostanę 10 dni na skończenie projektu, to choćbym wyrobił się w 3, nigdy się do tego nie przyznam. Nie opłaca mi się. Poza tym, mam płacone za każdą nadgodzinę. Im dłużej pracuję, tym więcej zarabiam.
Powiedziałeś, że łatwiej jest wdrażać nowe osoby do projektu. Też mi korzyść! A jeśli nowy będzie lepszy ode mnie? Wtedy ja wylecę z roboty. Piszę mój kod tak, żebym tylko ja mógł go zrozumieć. Być może praca z JSP liczącym 2000 wierszy nie jest zbyt wygodna, ale przynajmniej wiem, że będę potrzebny. Wiem, że będę miał pracę. Refaktoring jest mi zbędny.

Szczerze mówiąc nie wiedziałem co odpowiedzieć na takie dictum...Myślałem, że gdy podzielę się tą historią, to będzie mi lepiej. Nie jest ;/

Tuesday, June 24, 2008

Metaprogramy w tworzeniu oprogramowania



Jeśli jesteś programistą albo w jakikolwiek inny sposób związany z branżą IT, to najprawdopodobniej po przeczytaniu tytułu, spodziewałeś się porcji informacji technicznych. Muszę Cię zaskoczyć, artykuł będzie o innych programach i o innym programowaniu niż sobie wyobrażasz.

Ludzie posługują się swoimi myślami w zorganizowany sposób. Dzieje się to często na poziomie nieświadomym. Gdy o czymś myślimy, w umyśle pojawiają się różnego rodzaju obrazy, odczucia czy dźwięki. Choć nie zawsze zdajemy sobie z tego sprawę, w ten właśnie sposób przetwarzamy informacje docierające z zewnątrz (i z wewnątrz).

Psycholingwistyce zawdzięczamy badania nad tą wewnętrzną organizacją codziennych doświadczeń. Okazuje się, że można wyróżnić pewne schematy, że ludzie posługują się pewnymi wzorcami zachowań, które nazwano metaprogramami. Metaprogramy zostały pogrupowane w przeciwstawne, uzupełniające się pary scenariuszy w następujący sposób:
  • Unikanie – Dążenie
  • Autorytet wewnętrzny – Autorytet zewnętrzny
  • Podobieństwa – Różnice
  • Ja – Inni
  • Informacje szczegółowe – Informacje globalne
  • Proaktywne – Reaktywne
  • Opcje – Procedury

Dla osoby skoncentrowanej na unikaniu, bodźcem do działania będzie chęć uniknięcia niepożądanych sytuacji. Przeciwnie, dla osoby posługującej się metaprogramem Dążenie, ważniejsze będzie osiągnięcie wyznaczonego celu. Czytelników zainteresowanych dokładnym poznaniem metaprogramów odsyłam do literatury.

Preferowane metaprogramy


W praktyce nigdy nie jest tak, aby dany scenariusz np. Unikanie występował w czystej formie. Jest tak przede wszystkim dlatego, że procesy myślowe są w swej naturze zbyt skomplikowane (i jeszcze wiele jest do odkrycia), by opisać je za pomocą tak prostego modelu jakim są metaprogramy. Koncept metaprogramów należy traktować zatem jako pewne przybliżenie rzeczywistości o ograniczonym zakresie stosowalności.

Używanie metaprogramu jest zależne od kontekstu (kontekstualizm), środowiska w którym znajduje się człowiek. Ktoś na co dzień skoncentrowany na Dążeniu może radykalnie zmienić sposób myślenia, gdy ucieka przed goniącym go psem – wtedy najprawdopodobniej skupi się na uniknięciu niebezpieczeństwa, bez znaczenia w jaki sposób.

Bardzo istotne jest to, że metaprogramy nie podlegają ewaluacji. Żaden z nich nie jest lepszy lub gorszy – są inne. Jedyne co można powiedzieć to, że dany scenariusz może być mniej lub bardziej użyteczny w określonej sytuacji. Zadaniem artykułu jest, przede wszystkim, pobudzenie do refleksji nad własnym stylem pracy.

Choć obserwując zachowania ludzi, można wskazać pewne przejawy każdego z metaprogramów to jednak poszczególne osoby mają osobiste preferencje. Najczęściej 1 do 3 metaprogramów dominuje, pozostałe pojawiają się sporadycznie. W dalszej części zastanowimy się, które z metaprogramów są najistotniejsze w pracy programisty.

Zbieranie wymagań


Zbieranie wymagań oraz modelowanie systemu są czynnościami o tyle ważnymi, że kierunkują dalsze prace zespołu. Prace nad projektem rozpoczynają się (powinny się rozpoczynać) od stworzenia jego wizji. Wizja, w postaci metafory, jest metacelem, do którego zmierzać będzie projekt w kolejnych fazach rozwoju. Dalej wyłania się zbiór wymagań, zestaw procesów, w których może brać udział użytkownik. Pojawia luźny koncept rozwiązania i będzie uszczegóławiany w dalszych krokach.

W opisanym wyżej procesie projektowania zauważamy, że prace odbywają się na poziomie ogólnym. Programiści/projektanci wykazują w tym miejscu tendencję do skupiania się na konkretnym rozwiązaniu. Podczas rozmowy z potencjalnym użytkownikiem, od którego należy zebrać wymagania, deweloper stara się podążać za tokiem myślowym rozmówcy. Jednocześnie zastanawia się, czy i jak konkretne wymaganie zostanie zrealizowane. Myślenie o rozwiązaniu jest jak najbardziej w porządku tyle, że nie na to jest miejsce podczas zbierania wymagań, gdy należy się skoncentrować na zrozumieniu wyobrażeń użytkownika o sposobie pracy z przyszłym narzędziem. Z tego względu zbieranie wymagań warto przeprowadzać w sesjach, np.: godzinę rozmowy z użytkownikiem, a następnie pół godziny na analizę wymagań i formułowanie dodatkowych pytań.

W artykule Klient, który wie, czego chce rozważałem różnice w sposobie myślenia analityka i klienta. Głównym wnioskiem było to, że klienci jednak wiedzą, czego chcą. Opisują to jednak w postaci procesów, natomiast analityk widzi system jako strukturę. Podążając za procesem łatwiej dokonywać w nim zmian. Z kolei zmiany wprowadzane w strukturze wymagają przeanalizowania całej koncepcji od nowa. Podczas zbierania wymagań klient często zastanawia się nad pożądanym sposobem pracy. Próbuje różnych alternatywnych przebiegów tego samego procesu i oczekuje od analityka pomocy w wyborze najlepszego rozwiązania. Jeśli analityk nie podąża w procesie za klientem, lecz percypuje system jako strukturę, to ma nie lada kłopot. Alternatywne ścieżki procesu klienta zazwyczaj mają istotny wpływ na domniemaną strukturę systemu i/lub implementację.

Dochodzimy do kolejnego, istotnego w pracy programisty, metaprogramu: Opcje – Procedury. Najczęściej klient chce próbować różnych możliwości – jest nastawiony na opcje. Analityk powinien wyjść naprzeciw tym oczekiwaniom. Należy przypomnieć fakt, że nastawienie na opcje może nie być głównym metaprogramem klienta. Jednak w kontekście zbierania wymagań, gdy zastanawia się on nad sposobem pracy z narzędziem i analizuje różne procesy, najczęściej chce mieć możliwość wybrania najlepszego rozwiązania spośród dostępnych.

Podsumowując część o zbieraniu wymagań stwierdzamy, że w tym kontekście pracy programisty ważne jest nastawienie na Ogół oraz na Opcje. Te sposoby myślenia pozwalają pozostać na odpowiednim poziomie abstrakcji i skupić się na wizji i celu istnienia systemu zamiast brnąć w konkretne technologie i rozwiązania tylko po to, aby w następstwie późniejszych decyzji w pośpiechu się z nich wycofywać.

Programowanie


Po zebraniu wymagań i zaplanowaniu prac przychodzi czas na implementację. W chwili obecnej role projektanta oraz programisty przenikają w projekcie. Myślę, że to dobrze, gdyż można w pełni wykorzystać potencjał tkwiący w programistach. W tej sytuacji deweloperzy dostają pewne wytyczne z projektem systemu i mają dość dużą dowolność w implementowaniu poszczególnych klas oraz metod.

Implementacja złożonego systemu informatycznego, gdzie współpracuje ze sobą wiele niezależnych bytów, jest skomplikowanym zadaniem. Na tym etapie programujemy już konkretne zachowania i procesy. Od programisty oczekujemy, że będzie potrafił uzasadnić wybór tego, a nie innego rozwiązania. Dodatkowo należy przestrzegać obowiązujących w zespole konwencji kodowania, zasad projektowania, wytycznych pisania testów, itp. Nie ma tu miejsca na swobodną twórczość albo raczej ma ona miejsce drugorzędne. Ważniejsze jest przestrzeganie określonych zasad – procedur. Ujawnia się nam kolejny scenariusz pomocny programiście: Procedury. Osoba, sprawnie posługująca się procedurami, rozważa rzeczywistość jako ciąg logicznych następstw, gdzie każde zdarzenie ma swoją przyczynę oraz konsekwencje. Intuicyjnie czujemy, że Procedury to jeden największych sprzymierzeńców programistów. Możemy być dumni z faktu, że wykorzystujemy metaprogram do tworzenia wspaniałego oprogramowania.

Warto jeszcze poruszyć jeszcze jeden aspekt programowania, istotny w kontekście rozważania metaprogramów. Mowa o abstrahowaniu. Języki obiektowe dają sposobność budowania złożonych zależności pomiędzy klasami dzięki np. dziedziczeniu, polimorfizmowi, interfejsom. Możliwości te, aż same zachęcają do ich korzystania. Istota kwestii leży w sposobie korzystania z tych udogodnień. Gdy programista implementuje daną funkcjonalność ma tendencję do tworzenia całej gamy abstrakcyjnych bytów „na wyrost”. Efekt jest taki, że w projekcie każda klasa implementuje specyficzny interfejs (nie używany nigdzie indziej) oraz posiada, do niczego nie potrzebną, klasę abstrakcyjną. Z kolei podczas używania danych klas i tak wszędzie następuje rzutowanie na obiekty konkretne. Przyczyną takiego tworzenia kodu jest „zachłyśnięcie się” paradygmatem reużywalności i elastyczności komponentów. Oczywiście paradygmat ten jest jak najbardziej słuszny, lecz sposób jego egzekwowania już nie. Błąd programisty polega na próbie zaprojektowanie stworzenia maksymalnie rozszerzalnego kodu i „zapomina” że każde rozwiązanie ma ściśle określone granice stosowalności. Programista próbuje stworzyć komponenty możliwe do zastosowania wszędzie (Golden Hammer). Popatrzmy na poniższy kod:

public interface Problem {
//...
}
public interface Solution {
//...
}
public interface UniversalProblemSolver {
public Solution solve( Problem problem );
}


Właściwie każda klasa mogłaby implementować interfejs UniversalProblemSolver, prawda? Gdy tworzymy kod, który ma dostarczać określoną funkcjonalność i koncentrujemy się przede wszystkim na jego uniwersalności, zamiast na konkretnym zadaniu które ma wykonać, popadamy w opisaną wyżej pułapkę. W takiej sytuacji myślimy poprzez Ogół i tworząc abstrakcyjne byty, zmierzamy do celu wyjątkowo krętą ścieżką.

Optymalne programowanie wymaga podejścia od strony Szczegółu. Najpierw tworzymy konkretne rozwiązanie implementujące żądaną funkcjonalność. Jeśli w trakcie tworzenia kodu zajdzie potrzeba abstrahowania: wydzielenia hierarchii dziedziczenia, interfejsów – refaktorujemy kod. Praca odbywa się zatem od Szczegółu do Ogółu – w odwrotnym kierunku niż w przypadku zbierania wymagań i modelowania systemu. Ten styl programowania wymaga od deweloperów wewnętrznej samodyscypliny. Warto skorzystać z technik programowania np. Test-Driven Development, które wspomagają ten styl pracy. TDD, dzięki filozofii pisania testu funkcjonalności przed jej implementacją, zabezpiecza przed nadmiarowym „rozdmuchiwaniem” klas i dokładaniem kodu, być może użytecznego, lecz zbędnego z punktu widzenia tworzonego systemu.

Przenoszenie metaprogramów


Powyżej wyróżniłem dwa konteksty pracy programisty, w których pożądane jest posługiwanie się odmiennymi scenariuszami metaprogramów Ogół – Szczegół oraz Opcje – Procedury. Ta właśnie potrzeba przełączania się pomiędzy różnymi scenariuszami rodzi wiele kłopotów i frustracji. Jest tak przeważnie dla tego, że „uruchamianie” odpowiedniego metaprogramu odbywa się w sposób nieświadomy. Drugą przyczyną, jak na ironię, jest zdolność do uczenia się. Przypuśćmy, że ktoś uczy się programowania. Po jakimś czasie wykształca sobie pewien sposób myślenia i sprawnie posługuje się scenariuszami Procedury oraz Szczegół. W umyśle człowieka rodzi się skrót myślowy: jeśli TO zadziałało w TYM przypadku, z pewnością zadziała również w TAMTYM. Ów człowiek, za pomocą metaprogramu, dobrze działającego w jednym kontekście (praca) próbuje wykonywać działanie w zupełnie innym kontekście (np. relacje międzyludzkie). To zjawisko może prowadzić do niepożądanych skutków. Praca nad własnymi metaprogramami uwrażliwia nas na podobne sytuacje i uczy efektywnego funkcjonowania w różnych kontekstach.

Korzyści


Najważniejszą, moim zdaniem, korzyścią ze znajomości i rozwijania określonych metaprogramów jest rozszerzenie spektrum możliwych reakcji na sytuacje pojawiające się podczas prac nad projektem. Umiejętność posługiwania sposobami myślenia innymi niż nasze własne sprawia, że efektywniej współpracujemy z klientami, użytkownikami oraz z innymi członkami zespołu. Poniżej wymieniam dodatkowe korzyści:
  • pomoc w formowaniu zespołu projektowego
  • wyznacznik dla osób rekrutujących programistów
  • skrócenie czasu zbierania wymagań
  • poprawa komunikacji w zespole
  • pomoc podczas projektowania ścieżki rozwoju dla programistów


Podsumowanie


W artykule wyróżniłem metaprogramy Ogół – Szczegół oraz Opcje – Procedury jako bardzo istotne w pracy programisty. Zdaję sobie sprawę, że istnieje wiele punktów w procesie rozwoju oprogramowania, które nie zostały tu wspomniane. W obszarach tych również korzystamy ze specyficznych metaprogramów i wiedza o nich będzie nieocenioną pomocą. Dalszą eksplorację tego zagadnienia rezerwuję sobie na najbliższą przyszłość. Warto wskazać czego potrzeba opracowaniu tego typu, aby stanowiło rzetelną pomoc w pracy dewelopera:
  • kompletny opis procesu wytwarzania oprogramowania z punktu widzenia wykorzystywanych metaprogramów,
  • opis efektywnych technik pracy na każdym z etapów,
  • zestaw ćwiczeń pozwalających rozwijać pożądane metaprogramy.

Każdy krok w kierunku rozwoju własnego warsztatu pracy uświadamia, że istnieje coś poza technologią, coś co porządkuje poszczególne umiejętności i pozwala korzystać z ich jeszcze efektywniej. Odkrywanie tych zasobów jest fascynującą podróżą ku doskonałości.

Friday, June 13, 2008

Strategie decouplingu



W inżynierii oprogramowania występuje masowe dążenie do tworzenia systemów informatycznych z komponentów wielokrotnego użytku. Komponenty te mogą występować na różnym poziomie abstrakcji oraz złożoność. Artykuł poświęcony jest komponentom najniższego poziomu jakimi są klasy oraz ich obiekty.

Swobodna twórczość


O co właściwie chodzi z tym całym decouplingiem1? W gruncie rzeczy o przejrzystość, elastyczność i elegancję kodu. Wyobraźmy sobie następującą sytuację:

Pewien interfejs, SecurityManager, jest odpowiedzialny za zrealizowanie operacji autentykacji oraz autoryzacji. Po krótkiej analizie stwierdzamy, że chociaż przeprowadzenie operacji autentykacji należy do interfejsu SecurityManager, to już sama czynność sięgnięcia do bazy danych w celu pobrania potrzebnych informacji leży poza tą odpowiedzialnością. Na diagramie widać, że działania na bazie danych delegowane są do pomocniczego interfejsu UserCredentialsDAO.
Pojęcie decouplingu dotyczy powiązań pomiędzy poszczególnymi klasami/interfejsami w systemie, np.: powiązania pomiędzy implementacjami interfejsów SecurityManager i UserCredentialsDAO. Jak zatem zapewnić to powiązanie? Najprostszym możliwym sposobem – w konstruktorze.

public class JdbcSecurityManager implements SecurityManager {

public UserCredentialsDAO userCredentialsDAO;

public JdbcSecurityManager() {

userCredentialsDAO = new JdbcUserCredentialsDAO();

}


Szybko jednak okazuje się, że dana klasa wymaga więcej zależności, aby wykonać swoje zadanie. Dodatkowo chcemy mieć możliwość decydowania, które konkretne implementacje klas zależnych zostaną dostarczone do obiektu. W takim przypadku można zastosować parametryzowane konstruktory.
public class JdbcSecurityManager implements SecurityManager {

public UserCredentialsDAO userCredentialsDAO;

public SecurityHolder securityHolder;

public JdbcSecurityManager( UserCredentialsDAO credentialsDAO, 
SecurityHolder securityHolder ) {

this.userCredentialsDAO = credentialsDAO;
this.securityHolder = securityHolder;
}



Rozwiązanie to sprawia kłopot, gdy pisane są testy jednostkowe wykorzystujące klasę JdbcSecurityManager lub jej mocka. Niedogodność polega na tym, że czasem , teście jednostkowym chcemy mieć możliwość podmiany implementacji interfejsów UserCredentialsDAO oraz SecurityHolder. Z tego względu warto do tworzonej klasy , prócz parametryzowan/ch konstruktorów dodać również konstruktor domyślny oraz setery i getery do odpowiednich pól. W klasie wykorzystującej interfejs SecurityManager można wstawić następujący kod:

UserCredentialsDAO credentialsDAO = new JdbcUserCredentialsDAO();
SecurityHolder securityHolder = new SecurityHolder();

JdbcSecurityManager securityManager = new JdbcSecurityManager();
securityManager.setUserCredentialsDAO( credentialsDAO );
securityManager.setSecurityHolder( securityHolder );



Stosując opisaną powyżej strategię zapewniamy utworzenie powiązań pomiędzy obiektami w systemie. Jednocześnie sprawiamy, że obiekty są silnie uzależnione od siebie. Jest tak dlatego, że instancje poszczególnych klas są tworzone w kodzie – klasa nadrzędna tworzy instancje klas z nią współpracujących. O klasach, w których powiązania między nimi są realizowane w ten sposób mówimy, że są coupled. Konsekwencją takiego podejścia jest to, że jakakolwiek podmiana poszczególnych implementacji wiąże się z ingerencją w kod źródłowy. Taki kod jest nieelastyczny oraz trudny w utrzymaniu i rozwoju.

Przeciwwagą kodu, w którym klasy są coupled, jest kod w którym są one decoupled, to znaczy w maksymalnym stopniu od siebie niezależne. Współpracują ze sobą, ale można w łatwy sposób wymieniać poszczególne implementacje, a ingerencja w projekt jest ograniczona do minimum. Czynność zapewniania takiego stanu rzeczy nazywa się decoupling (lub po lekkim spolszczeniu decouplingiem) i jest przedmiotem niniejszego artykułu.

Fabryka


Ponownie odwołując się do pojęcia odpowiedzialności obiektów można zapytać, czy tworzenie nowych obiektów należy do odpowiedzialności klasy JdbcSecurityManager? Nie, gdyż jej zadaniem jest przeprowadzać operacje związane z bezpieczeństwem. Obiekt tej klasy nie może wykonywać swoich działań gdy nie ma obiektów pomocniczych, do których deleguje odpowiednie operacje. Remedium przychodzi w postaci scentralizowanego miejsca, w którym tworzone będą obiekty używane w systemie. Można zaimplementować to rozwiązanie w postaci wzorca SimpleFactory.

public final class ObjectFactory {
public static SecurityManager createSecurityManager( 
String type ) {

SecurityManager securityManager = //...  
return securityManager;
}
public static UserCredentialsDAO createUserCredentialsDAO( 
String type ) {
//... 
}
}



Metody fabryki muszą zdecydować, w jaki sposób należy zbudować dany obiekt, np. którą implementację należy utworzyć. Decyzja zostanie podjęta na podstawie parametru type metod fabryki.

public class BankTransferManager {
private SecurityManager securityManager;

public BankTransferManager() {
securityManager = ObjectFactory.createSecurityManager(                                                             
"securityManager" );
} 
}


W powyższym rozwiązaniu tworzenie klas i powiązań pomiędzy nimi zostało oddelegowane do osobnego obiektu fabryki. Dzięki temu nie klasa decyduje o użytych implementacjach lecz fabryka. Rozwiązanie zapewnia decoupling kodu, gdyż konkretne klasy nie są ze sobą trwale związane (np. poprzez tworzenie zależności w konstruktorach), zatem można w dowolnym momencie podmieniać implementacje modyfikując odpowiednio działanie klasy ObjectFactory. Oczywiście ingerencja w kod jest nieodzowna, lecz dotyczy tylko jednego obiektu, nie wszystkich.

Singleton

Czasem zachodzi konieczność, aby dany obiekt występował tylko raz w systemie, np. SecurityManager jeśli nie chcemy, próba autentykacji użytkownika była podejmowana przez dwa byty.
Może być też tak, że nie ma sensu tworzyć wielu bezstanowych obiektów zajmujących się tylko logiką, np. UserCredentialsDAO, gdyż w zupełności wystarczy tylko jeden.
W powyższych wypadkach stosuje się wzorzec Singleton, aby zapewnić, że dany obiekt zostanie utworzony tylko raz. Poniżej znajduje się przykładowa implementacja Singletonu.

public class LdapSecurityManager implements SecurityManager {

private static LdapSecurityManager instance;

private static LdapSecurityManager getInstance() {
if ( instance == null ) {
instance = new LdapSecurityManager();
}
return instance;
}
} 


Klasa ObjectFactory musi skorzystać z metod getInstance() do pobrania obiektu. Jeśli singleton ma być używany w aplikacji wielowątkowej musi być thread-safe. Za Williamem Pughem podaję taką implementację:

public class LdapSecurityManager implements SecurityManager {

private static class SingletonHolder {
private final static LdapSecurityManager instance
= new LdapSecurityManager();
}

private static LdapSecurityManager getInstance() {
return SingletonHolder.instance;
}
}


Należy pamiętać, że jeśli system działa na wielu maszynach wirtualnych, to klasa singletonu będzie ładowana na każdej z nich. Z tego względu zaleca się unikać tego rozwiązania w systemach rozproszonych.

Service Locator

Uogólnieniem ObjectFactory jest wzorzec Service Locator, którego zadaniem jest dostarczenie klientowi żądanej klasy usługowej, z tą różnicą, że klient nie wie skąd pochodzi dana usługa – czy jest lokalna, czy też zdalna.

Kontekst aplikacji


Alternatywą dla ObjectFactory jest zastosowanie obiektu, w którym przechowywany jest tzw. kontekst aplikacji. Kontekst zawiera w sobie wszystko to, czego system potrzebuje do poprawnego działania, np. obiekty realizujące konkretne usługi.

public class LdapSecurityManager implements SecurityManager {

public void authenticate( UserCredentials credentials,
AppContext context ) {

UserCredentialsDAO credentialsDAO
= context.get( "userCredentialsDAO" );
}
}


Każda metoda, w której potrzeba użyć usługi przechowywanej w kontekście będzie przyjmować dodatkowy parametr – AppContext, który umożliwia pobranie potrzebnego obiektu. Oczywiście podczas startu aplikacji należy najpierw zbudować odpowiedni kontekst.

Kiedy zatem używać obiektu kontekstu, a kiedy fabryki? Obiekt kontekstu umożliwia przechowywanie aktualnego stanu aplikacji do którego powinny mieć dostęp wszystkie obiekty (przechowywanie stanu w zewnętrznym obiekcie dostępnym poprzez metody statyczne jest mało eleganckie i nieintuicyjne). Fabryka skupia się tylko na tworzeniu obiektów. Konkretny wybór zależy o bieżących potrzeb. Kontekst, podobnie jak fabryka, zapewnia decoupling obiektów.

Dependency Injection


Konsekwencją stosowania zarówno fabryki jak i kontekstu aplikacji jest sytuacja, w której dana klasa musi zażądać obiektów pomocniczych, których chce użyć, od fabryki lub pobrać je z kontekstu aplikacji. Zatem dany obiekt dba o to, aby odnaleźć potrzebne mu obiekty współpracujące za pomocą fabryki, lokatora lub konktestu. Obiekt dba o swoje zależności.

Przeformułujmy problem w następujący sposób: żądamy takiej architektury aplikacji, w której dostarczony zostanie nam obiekt gotowy do użycia z już rozwiązanymi zależnościami. Takie podejście nosi nazwę Dependency Injection. Obiekty są zarządzane przez tzw. kontener. Przed użyciem należy zdefiniować obiekty oraz powiązania pomiędzy nimi, a następnie pobierać obiekty z kontenera i używać ich w systemie.



Na rysunku widać schemat Dependency Injection. Wszystkie obiekty oraz ich wzajemne relacje zdefiniowane są w zewnętrznym pliku XML, np:

<bean id="userCredentialsDAO" 
class="mbartyzel.decoupling.JdbcUserCredentialsDAO" />
<bean id="securityManager" 
class="mbartyzel.decoupling.JdbcSecurityManager">
<property name="credentialsDAO" ref="userCredentialsDAO" />
</bean>

Natomiast w systemie pobierane są obiekty z kontenera:
SecurityManager securitManager = BeanFactoryHolder
.getBean( "securityManager" );


Przykłady frameworków dostarczających kontenera DI to np.: SpringFramework, PicoContainer, Google-Guice.

Aby kod aplikacji był decoupled od frameworka oczekujemy, aby nie narzucał zarządzanym przez siebie obiektom implementowania specyficznych interfejsów. Gdyby tak było, kod stałby się zbyt zależny od samego framewokra. Niemożliwa byłaby wtedy sprawna zamiana jednego kontenera na inny, a testowanie utrudnione.

Podsumowanie


W artykule omówione zostały strategie decouplingu w systemach informatycznych takie jak: fabryka, Service Locator, kontekst aplikacji, kontener Dependency Injection. Autor ma nadzieje, że podane przykłady implementacji pozwolą Czytelnikom poprawić jakość tworzonego kodu. Nadrzędnym celem było wskazanie kilku możliwości osiągnięcia tego samego efektu. Mając wybór możemy, w danej sytuacji, świadomie decydować o przewadze jednego rozwiązania nad innym. Pamiętajmy, że jeśli jednym dostępnym narzędziem jest młotek, wszystko zaczyna wyglądać jak gwóźdź...

Wednesday, June 11, 2008

Historia jednego JUGa

Czasem tworzysz coś planowo, czasem coś zwyczajnie się dzieje, lub też planujesz a i tak rzeczy biegną swoim torem. Nie inaczej był z JUGiem łódzkim.

Ubolewaliśmy z Mariuszem, że wcześniejsze plany nie pozwolą nam zjawić się ja Javarsovi. W pewnym momencie zaczęła się dyskusja o starych pomysłach utworzenia w Łodzi jakiejś javowej społeczności. No, więc szukamy w necie, szukamy i nic. Wyobrażacie sobie?!? Łódź - ponad milion ludzi, centra akademickie, kilka poważnych firmy developerskich, a lokalnej społeczności Java ani widu ani słychu.

Nieśmiało założyłem grupę na gmail, wysławszy następnie info na JDN. Następnego dołączył Tomek i wrzucił pierwsze logo, porozmawialiśmy trochę pogratulowaliśmy sobie powstania JUGa i tyle. Potem pojawił Bartek ze swoim zapasem niekończącej się energii:). Długo rozmawialiśmy na GG, Bartek jednocześnie spamował :) do JackaL z WawaJUG z prośbą o podzielenie się doświadczeniami. Na koniec dnia mieliśmy dość mglistą wizję: Co, gdzie, kiedy i jak...

Szczerze mówiąc potem już straciłem nad tym, kto kiedy dołączył i co powiedział. Jakoś tak z niczego pojawiły się logo, zjawili się studenci z PŁ z propozycją udostępnienia sal, koledzy z innych JUGów aktywnie właczyli się do dyskusji...mój Thunderbird pracował jak oszalały.

Spotkaliśmy się w sobotę 7 czerwca by proklamować powstanie łódzkiego JUGa. Doszliśmy do wniosku, że przez wakacje będziemy testować działanie społeczności, aby pełną parą ruszyć we wrześniu/październiku.
Pierwsze spotkanie odbędzie się już za miesiąc 5 lipca i poruszać się będziemy wokół EJB3.

Mam nadzieję, że entuzjazm i energia zostaną utrzymane na długo, a rodząca się społeczność jest zaczątkiem czegoś wielkiego.