#21 – Wzorce projektowe

By 19 marca 2015Kurs Javy
Wpis-Header lekcje (1)

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!).

Materiały dodatkowe / dokumentacja

  1. Lista wzorców na wikipedii (EN)
  2. Amazon: GOF Design Patterns (EN)

Licencja Creative Commons

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!

  •  
  •  
  •  
  •  
  •  
  • Krzysztof Ambroziak

    Mam prośbę, możecie wytłumaczyć różnicę jaka jest między fabryką a metodą wytwórczą? Do dziś nie widzę różnicy, a wiem, że jest.

    • Cześć,
      jako fabrykę jak rozumiem masz na myśli wzorzec Abstract Factory :) W takim wypadku różnica polega na tym, że w AbstractFactory definiujemy interfejs, który zawiera jedną lub więcej Factory Method (metod wytwórzczych). Implementacje tego interfejsu mogą zawierać różną logikę w tej metodzie, za każdym razem jednak zwracając obiekt tego samego typu (lub implementujący ten sam interfejs).

      Myślę, że kwestia tutaj jest bardziej dokładniejszego nazewnictwa :)

  • Jacek

    Zastanawiam się nad użyciem wzorca fasada, a sytuacja wygląda następująco: mam 5 klas serwisów, z których każdy obsługuje metody odpowiedniego dla siebie repozytorium, dodatkowo wstrzykuję te serwisy do innych serwisów, bo logika jest trochę skomplikowana. Serwisy te mają także metody prywatne. Zastanawiam się czy kierując się wzorcem fasady stworzyć ziarna stateless, które udostępniałyby metody wykorzystywane w kontrolerach i endpointach REST? Zastanawiam się czy wpłynie to korzystnie na czytelność aplikacji i jej odpowiednie rozwarstwienie czy wprowadzi niepotrzebne linie kodu.