Zapewne już nieraz spotkałaś się z określeniem wzorców projektowych, dzisiaj przybliżymy sobie czym są oraz jak je stosować (i jak ich nie stosować)
Wzorce projektowe są nieodłącznym przyjacielem programisty — pozwalają pisać czystszy kod, łatwiejszy do zrozumienia przez innych i zapewniają pewien abstrakcyjny zbiór rozwiązań abstrakcyjnych problemów. Wbrew częstemu przekonaniu, nie są one gotowymi rozwiązaniami! Cytując p. Martina Fowlera: “patterns are half-baked — meaning you always have to finish them yourself and adapt them to your own environment“1 — to tylko półprodukty rozwiązania.
1) Martin Fowler, http://martinfowler.com/ieeeSoftware/patterns.pdf
Lekcja
Wstęp
Mówiąc o wzorcach projektowych nie wypada nie powiedzieć najpierw o ich de facto standaryzacji, czyli kultowej już książce “Design Patterns: Elements of reusable Object-Oriented Software”, którą wielu zna pod określeniem ‘GoF Design Patterns” (GoF to skrót of Gang of Four — książka ma czterech autorów). W publikacji tej nie tylko opisano najpopularniejsze wzorce, podjęto także — udaną — próbę ustandaryzowania opisu. Dzięki temu współcześnie różne wzorce i pomysły są opisane w podobny sposób, zawierając kluczowe informacje o każdym wzorcu:
- Nazwa i klasyfikacja wzorca
- Cel — co pozwala osiągnąć
- Motywacja — opisuje cel wzorca (co stara się osiągnąć / jaki problem wyeliminować) oraz powodu, dla których należy go użyć
- Zastosowanie — opisuje sytuacje, problem, który sugeruje użycie wzorca
- Struktura — wizualna reprezentacja (najczęściej w formie diagramu UML lub innego, koncepcyjnego)
- Uczestnicy — lista klas i obiektów, które biorą udział w implementacji tego wzorca
- Współpraca — opis, w jaki sposób powyższe elementy współpracują ze sobą, jakie są między nimi interakcje
- Konsekwencje — wynik uzycia wzorca, koszty jego stosowania i kompromisy
- Implementacja — opis implementacji (np. w pseudokodzie lub normalnym tekstem)
- Przykład — przykład implementacji w wybranym języku
- Znane zastosowania — przykłady użycia (np. w popularnych bibliotekach, narzędziach
- Powiązane wzorce — podobne lub komplementarne wzorce
Dodatkowo wyróżniamy podstawowy podział wzorców na cztery kategorie:
- Kreacyjne opisują, w jaki sposób obiekty są tworzone
- Behawioralne opisują zachowanie obiektów
- Strukturalne opisują sposób, w jaki obiekty są zbudowane
- Architektoniczne (wprowadzone później) opisują bardziej abstrakcyjne wzorce jak np. MVC
Temat ten jest baardzo szeroki i zapraszam do lektury podlinkowanych materiałów, książek i innych źródeł — warto przynajmniej przejrzeć, żeby w przyszłości kojarzyć i wiedzieć, gdzie wrócić po informacje. Ostrzegam jednak przed materiałami uczelnianymi (prezentacje, slajdy) — niestety, niektóre z nich są bardzo nierzetelne, raczej zachęcam do lektury blogów o programowaniu, architekturze, publikacji poświęconych tylko temu zagadnieniu.
W dzisiejszej lekcji jedynie poruszymy kilka wzorców, które wg nas są ważne na początku przygody z programowaniem.
Wzorzec: Fasada
Wzorzec fasady polega na tym, że tworzymy klasę, której jedynym zadaniem jest wywoływanie odpowiednich metod z innych klas (np. serwisów) czasem w odpowiedniej kolejności lub dodając/modyfikując pewne informacje.
Jako przykład (choć nie do końca prawidłowy, ale za to znany wszystkim czytelniczkom) możemy podać kontrolery w aplikacji webowej — najczęściej ich działanie ogranicza się do prostej logiki i wywołania odpowiedniego serwisu (lub kilku serwisów).
W praktyce fasady często spotykamy w sytuacjach, kiedy mamy wiele różnych systemów (np. w korporacjach), a potrzebujemy spójnego sposobu na dostęp do danych z róznych źródeł (np. dla działu marketingu potrzebujemy danych ze sprzedaży oraz z magazynu). Fasada ułatwia dostęp do różnych obiektów i ukrywa szczegóły implementacji.
Wzorzec: Fabryka
Wzorzec fabryka (ang. factory method) to metoda, która tworzy nam nowy obiekt. Powodów, dla których chcemy tak zrobić może być wiele — najczęściej jako typ zwracany deklarujemy interfejs, a metoda zawiera logikę która decyduje jakiego typu obiekt utworzyć:
public Zwierze rozpoznajZwierzaka(String dzwiek) {
if (dzwiek.equals("hau")) {
return new Pies();
} else {
return new Kot();
}
}
Oczywiście są przypadki, że logikę realizowaną przez metodę-fabrykę moglibyśmy zaszyć w konstruktorze, ale nie zawsze jest to wskazane. Takie podejście pozwala na rozszerzenie funkcjonalności w przyszłości za pomocą technik OOP, np. dodanie obsługi nowych klas czy zmiana algorytmu.
Przykład użycia fabryki to np. konfiguracja Springa za pomocą kodu Javy (używając metod z adnotacjami @Bean).
(Anty)wzorzec: Singleton
Tutaj słówko wyjaśnienia (szczególnie dla osób z wykształceniem IT, które były uczone tego wzorca jako podstawy). To nie wzorzec jest problemem, ale jego powszechna percepcja i niezrozumienie. Niestety wielokrotnie prowadząc rozmowy rekrutacyjne na pytanie “jaki jest wg ciebie najważniejszy wzorzec projektowy” spotykałem się z odpowiedzią “Singleton”, po czym po kolejnym pytaniu “ok, zatem podaj mi jego zastosowania” nastawała cisza.
Sam wzorzec mówi o tym, że w systemie będzie używany tylko jeden obiekt danego typu. Standardowa implementacja (“uczelniana”) wygląda następująco:
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance==null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {} //prywatny konstruktor
}
Problem z takim podejściem jest związany z tym, że większość współczesnych aplikacji jest wielowątkowa (np. aplikacje webowe — dzieje się to często zupełnie bez naszej wiedzy) i rozproszona, tego rodzaju implementacja to prosty przepis na katastrofę w kwestii wydajności. Dodajmy do tego absolutną niemożliwość korzystania z koncepcji OOP (np. rozszerzenie / modyfikacji sposobu działania poprzez dziedziczenie) Są inne możliwości realizacji, które nadal możemy nazywać singleton (idea zostaje zachowana — mamy tylko jedną instancję, ale nie ‘wymuszamy’ tego, a raczej konfigurujemy w ten sposób). Przykładem użycia tego wzorca jest Spring Framework — domyślnie każdy element, który oznaczymy jako @Service jest singletonem (ale nie musi być — wiele rzeczy może spowodować, że tak nie będzie). Ważne żeby zapamiętać, że singleton jest nadal przydatnym wzorcem, ale jego implementacja rodem z uczelnianych slajdów ma swoje miejsce na śmietniku historii i jednowątkowych aplikacji.
Podsumowanie
Po tej lekcji powinnaś mieć świadomość, czym są wzorce oraz gdzie szukać informacji na ich temat. W praktyce na początku często będziesz sięgać do źródeł żeby poszukać pasującego wzorca, z czasem większość z tych używanych na codzień wejdzie Ci w nawyk i nie będziesz nawet dostrzegać, że ich używasz :) Szczególnie polecam książki wspomniane w treści, warto mieć którąś z nich zawsze pod ręką.
To, co trzeba koniecznie zapamiętać (i wyryć w ścianie, względnie wydrukować i powiesić nad biurkiem) — wzorce projektowe nie są odpowiedzią na problemy! Są wskazówkami dojścia do rozwiązania, ale nie stanowią odpowiedzi. Można je bardzo łatwo nadużyć. Absolutnie niedopuszczalna jest sytuacja, w której ‘naciągamy’ problem tak, aby pasował do konkretnego wzorca (nie, ‘nawet troszeczkę’ też jest niedopuszczalne!).
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!