BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Wzorce projektowe – czy jest mi to potrzebne?

25-01-2005 14:20 | elyast292
„Wzorzec projektowy pozwala uczyć się na sukcesach innych zamiast nauki na własnych błędach”[Mark Johnson]. Postaram się przybliżyć państwu zagadnienie wzorców projektowcyh na przykładzie prostego systemu do obsługi zawodów w programowaniu zespołowym.

Motto

„Wzorzec projektowy pozwala uczyć się na sukcesach innych zamiast nauki na własnych błędach” Mark Johnson

Definicja

De facto wzorzec projektowy jest sposobem na rozwiązanie pewnej klasy problemu. Każdy wzorzec projektowy posiada cztery podstawowe elementy:

a) nazwę, która identyfikuje problem,

b) problem, który opisuje, w jakich okolicznościach możemy użyć danego wzorca,

  1. rozwiązanie, czyli sposób na eleganckie rozwiązanie problemu,

  1. konsekwencje użycia danego wzorca.


Typy wzorców projektowych

Często wzorce projektowe utożsamia się jedynie z takimi pojęciami jak fabryka, singleton, mediator, itp. Wzorzec projektowy może jednakże występować podczas wielu etapów projektowania:

  • projektowania architektury systemu,

  • projektu mechanistycznego,

  • projektu szczegółowego.


Podczas projektowania architektury systemu możemy spotkać się z następującymi typami wzorców:

  • wzorce rozmieszczenia komponentów systemu,

  • wzorce architektoniczne, np. PCMEF,

  • frameworki, np. ERP, MVC.


Natomiast w trakcie tworzenia projektu mechanistycznego zwykle będziemy mieli do czynienia z wzorcami rozwiązującymi pewną klasę problemu:

  • tworzenie obiektów, np. abstrakcyjna fabryka, singleton,

  • grupowanie obiektów w większe struktury, np. fasada, adapter, dekorator,

  • sposoby komunikacji między obiektami, np. obserwator, mediator.


Projekt szczegółowy skupia się na zaprojektowaniu odpowiednich struktur danych(przykładem niech będzie stos, kolejka, słownik).

W poniższym artykule skupię się na dwóch pierwszych etapach projektowania. Postaram się przedstawić zagadnienie wzorców projektowych na przykładzie systemu służącego do obsługi zawodów w programowaniu zespołowym.


Przykładowa aplikacja

Przedstawmy wymagania funkcjonalne systemu:

  1. Możliwość wysyłania rozwiązań zawodników do sędziów.

  2. Możliwość oceniania przez sędziów rozwiązań zawodników.

  3. Administrator definiuje konta zawodników i sędziów zadań.

  4. Najpierw trzeba się zalogować, aby móc używać systemu.

 

Rysunek 1. Model przypadków użycia prostego systemu obsługującego zawody

Wzorzec rozmieszczenia

Na początku rozważmy projekt architektury systemu, a konkretnie rozmieszczenie komponentów naszej aplikacji. Początkujący programista wybrałby model klient-serwer. Klient zawierałby kod warstwy pośredniej, która obsługuje połączenie z serwerem bazy danych, a logikę biznesową zaszylibyśmy w procedurach składowanych bazy danych. Istnieje tutaj kilka problemów:

  • nie każdy serwer bazy danych obsługuje tworzenie procedur składowanych,

  • procedury składowane są trudne do przeniesienia pomiędzy różnymi systemami zarządzania bazą danych.


Wydaje się, że logikę biznesową łatwiej byłoby zaszyć w serwerze aplikacji, np. w postaci usługi WWW. Rozważmy zatem różne wzorce rozmieszczenia, które pomogą nam wybrać odpowiedni model dla naszej aplikacji:

  • klient – serwer (gruby klient, serwer bazy danych),

  • webowa architektura trójwarstwowa (cienki klient - przeglądarka, serwer www, serwer bazy danych),

  • aplikacyjna architektura trójwarstwowa (cienki klient, serwer aplikacji, serwer bazy danych),

  • architektura czterowarstwowa(cienki klient, serwer www, serwer aplikacji, serwer bazy danych).


W naszym przypadku chcemy skonstruować aplikację desktopową, do wyboru pozostają nam więc dwie architektury: klient-serwer i aplikacyjna architektura trójwarstwowa. Ponieważ chcemy, aby nasza aplikacyjna była przenaszalna pomiędzy różnymi serwerami bazy danych wybierzemy aplikacyjna architekturę trójwarstwową. Poniżej zaprezentujmy schemat tego rozmieszczenia:

Rysunek 2. Model rozmieszczenia rozważanego systemu

Wzorzec architektoniczny

Następnie rozważmy architekturę naszego systemu. Możemy kod obsługujący połączenia z bazą danych umieścić razem z kodem prezentującym dane. Wyobraźmy sobie sytuację, gdy zdecydujemy się jednak na stworzenie strony www zamiast aplikacji desktopowej. W tym przypadku należałoby przepisać cały kod zarządzający dostępem do bazy danych. Chciałbym przedstawiać wzorzec architektoniczny PCMEF+ (Maciaszek, V KKIO). Ideą tego wzorca projektowego jest oddzielenie warstwy prezentacji od logiki biznesowej oraz od kodu warstwy pośredniej zarządzającej dostępem do bazy danych. Rozważmy zatem własności tego wzorca wg [3]:

Rysunek 3. Wzorzec architektoniczny PCMEF+ wg [3]

  1. Zależności pakietowe są jednokierunkowe począwszy od warstwy prezentacji(presentation) do warstwy obiektów trwałych(foundation).

  2. Warstwa niższa w hierarchii jest warstwą usługową dla warstwy wyższej.

  3. Istnieje możliwość komunikacji z sąsiadującym pakietem (w tym przypadku pakiety entity i mediator).

  4. Klasy warstwy wyższej posiadają jawne asocjacje do obiektów warstwy niższej.

  5. Nie ma możliwości wystąpienia zależności cyklicznej pomiędzy pakietami.

  6. Istnieje pakiet znajomości, który jest dostępny dla każdej innej warstwy, jednak on sam nie wie nic o innych pakietach(pakiet acquaintance).


Część własności tego wzorca może być pilnowane przez Visual Studio .NET, jeśli tylko odpowiednio zaplanujemy tworzenie aplikacji. Naturalnym podejściem byłoby stworzenie jednego dużego projektu, jednakże przy dużej ilości klas byłby on trudny do zarządzania. Jeśli natomiast dla każdej warstwy wzorca PCMEF+ stworzymy osobny projekt, otrzymamy dużo nowych możliwości:

  • możemy rozdzielić projekty pomiędzy członków zespołu specyfikując odpowiednio interfejsy pomiędzy warstwami

  • nie mamy możliwości stworzenia cyklicznej zależności między pakietami, ponieważ VS .NET nam na to nie pozwoli

  • możemy odseparować ludzi tworzących GUI od specjalistów zajmujących się infrastrukturą aplikacji

Rysunek 4. Podział warstw na poszczególne projekty

Poniżej przedstawiam, w jaki sposób w kodzie C# uzyskać resztę własności wzorca PCMEF+:

  • Podział aplikacji na warstwy zgodne ze wzorcem - wystarczy wykorzystać mechanizm języka C#, a mianowicie przestrzenie nazw. Przykład:

[Kod C#]

namespace Zawody.Domain.Mediator

{

   /// <summary>

   /// Summary description for LoginService.

   /// </summary>

   public class LoginService : System.Web.Services.WebService

   {

      ...

   }

}

Klasę LoginService zawarliśmy w warstwie domain, co oznacza, iż klasa ta będzie pośredniczyła w dostępie przez warstwę logiki aplikacji do warstwy obiektów trwałych.


  • Specyfikacja jawnych asocjacji.

Asocjację (relację unarną, bądź binarną) będziemy przedstawiali w prosty sposób – jako atrybut odpowiedniej klasy (w zależności od liczności asocjacji i jej skierowania).

Pozwoliłem sobie graficznie przedstawić asocjacje między klasami za pomocą języka UML. Asocjacje unarne będziemy oznaczali poprzez łuk skierowany, natomiast binarne - łukiem nieskierowanym. Liczność asocjacji będziemy oznaczali na końcach łuków. Brak oznaczeń liczności na końcach łuków oznacza liczność 1.

Rysunek 5. Asocjacja unarna

 

Rozważmy asocjację unarną pomiędzy klasami UserView i LoginManager. W kodzie tę zależność możemy przedstawić następująco:

[Kod C#] 

public class UserView : Zawody.Presentation.View

{

   LoginManager loginManager = new LoginManager();

}

 

Rysunek 6. Asocjacja unarna z licznością 0 lub więcej

 

Następnie rozważmy asocjację unarną pomiędzy klasami LoginService i LoginManager. Jednakże w tym przypadku możemy stwierdzić, iż obiekt klasy LoginService może być w relacji z wieloma obiektami typu AccountDetails (0 lub więcej). Zależność tą przedstawiamy w kodzie, np. tak:

[Kod C#]

public class LoginService : System.Web.Services.WebService

{

   AccountDetails[] accounts = null;

}

Rysunek 7. Asocjacja binarna

 

Na koniec zwróćmy uwagę na asocjację binarną. W tym przypadku mamy do czynienia z asocjacją o liczności 1. Zależność tą możemy przedstawić następująco:

[Kod C#]

public class MainFrame : System.Windows.Forms.Form

{

   View view = null;

}


public class UserView : Zawody.Presentation.View

{

   Form parent = null;

}

Wzorzec mechanistyczny

Skoro już architektura systemu jest nam znana przejdźmy do szczegółów implementacji. Ponieważ możemy użyć wielu wzorców mechanistycznych podczas tworzenia kodu, chciałbym ograniczyć się tylko do jednego zagadnienia - mianowicie sposobu tworzenia interfejsu użytkownika.

Zauważmy, iż zwykle w poważnych aplikacjach mamy do czynienia z różnymi typami użytkowników o ściśle określonych interfejsach. Powstaje naturalne pytanie, w jaki sposób utworzyć warstwę prezentacji, skoro w głównej mierze zależy ona od typu użytkownika, który zalogował się do systemu. Proste rozwiązanie, które od razu nasuwa się do głowy jest następujące: stwórz jeden interfejs dla wszystkich, a w zależności od typu zalogowanego użytkownika udostępnij lub ukryj pewne opcje w programie. To rozwiązanie przysparza nam jednak następujących problemów:

  • jeśli chcielibyśmy rozszerzyć naszą aplikację o nowe typy użytkowników, to nasza klasa odpowiedzialna za tworzenie menu i widoków znacznie by się rozrosła, co z kolei sprawia, że będzie ona mało czytelna i "błędogenna",

  • jeśli natomiast jakaś funkcja systemu wykonuje się inaczej w zależności od typu użytkownika, to może się okazać, że musimy powielić daną opcję w menu (np. pobierz wyniki dla drużyn, pobierz wyniki dla sędziów) albo przy każdej "przeciążonej" funkcji będziemy musieli użyć instrukcji warunkowej, co przy nowych typach użytkowników narazi nas na poprawki kodu w wielu miejscach.

Okazuje się jednak, że rozwiązanie naszego problemu jest dość proste. Zastanówmy się o co nam chodzi – chcemy, aby tworzenie menu dla konkretnego użytkownika było przeźroczyste. Niech pewien obiekt w zależności od typy zalogowanego użytkownika utworzy nam całe menu z odpowiednią obsługą zdarzeń na odpowiednich elementach menu. Wzorzec abstrakcyjnej fabryki jest tym, czego szukamy. Idea jest następująca:

  • FabrykaAbstrakcyjna jest klasą, który zapewnia jedną funkcjonalność – tworzy abstrakcyjne produkty, których wymaga klient,

  • ProduktAbstrakcyjnyA oraz ProduktAbstrakcyjnyB są abstrakcją komponentów, jakich wymaga nasz klient, w naszym przypadku będzie to po prostu View – interfejs dla użytkownika,

  • Następnie tworzymy konkretne fabryki, dziedziczące po naszej abstrakcyjnej fabryce,

  • Konkretne fabryki będą nam tworzyły konkretne produkty, w naszym przypadku, np. fabryka AdminViewFactory utworzy nam fabrykę, która stworzy nam odpowiedni widok - AdminView.

Możemy wyróżnić następujące zalety tego wzorca:

  • Dodawanie nowych typów użytkowników jest proste – wystarczy napisać nowe klasy dla konkretnego produktu i konkretnej fabryki, a następnie poprawić obsługę logowania użytkownika - należy dodać kod tworzący odpowiednia fabrykę dla danego typu użytkownika,

  • Kod związany z obsługą jednego typu użytkownika jest zgromadzony w jednym miejscu, co pozwala rozdzielić pracę pomiędzy różnych członków zespołu,

  • Możemy wprowadzić hierarchię produktów, które będą tworzyły nam fabryki, żeby nie powielać pewnych fragmentów kodu,

Poniższej przedstawiam diagram klas w UML oraz przykładowy kod, który ilustruje wzorzec abstrakcyjnej fabryki.

Rysunek 8. Wzorzec projektowy – Abstrakcyjna Fabryka. Źródło [3].

Abstrakcyjna fabryka, która posiada metodę tworzącą widok – interfejs użytkownika.

[Kod C#]

public interface AbstractFactory

{

   View createView(Form parent);

}

Konkretna fabryka, która tworzy interfejs drużyny.

[Kod C#]

public class TeamFactory : Zawody.Presentation.AbstractFactory

{

   public View createView(Form parent)

   {

      return new TeamView(parent);

   }

}

Konkretna fabryka, która tym razem tworzy interfejs administratora.

[Kod C#]

public class AdminFactory : Zawody.Presentation.AbstractFactory

{

   public View createView(Form parent)

   {

      return new AdminView(parent);

   }

}

Konkretny produkt – interfejs użytkownika (zapewnia funkcjonalność logowania do systemu)

[Kod C#]

public class UserView : Zawody.Presentation.View

{

}

Konkretny produkt – interfejs drużyn (funkcjonalność logowania dziedziczy po widoku użytkownika).

[Kod C#]

public class TeamView : Zawody.Presentation.UserView

{

   TeamSolutionManager solutionManager;

}

Konkretny produkt – interfejs administratora

[Kod C#]

public class AdminView : Zawody.Presentation.UserView

{

   AccountManager accountManager;

}

Klient – posiada referencję do widoku.

[Kod C#]

public class MainFrame : System.Windows.Forms.Form

{

   View view = null;

}

Obsługa logowania – w zależności od typu użytkownika, tworzymy odpowiednią fabrykę, a następnie tworzymy odpowiedni widok i przekazujemy referencję klientowi.

[Kod C#]

private void login_Click(object sender, EventArgs e)

{

   AbstractFactory factory = null;

   int typeOfUser = loginManager.login("root", "secret");

   switch(typeOfUser)

   {

      case UsersConstants.ADMIN :

              factory = new AdminFactory(); break;

      case UsersConstants.REFEERE :

              factory = new RefereeFactory(); break;

      case UsersConstants.TEAM :

              factory = new TeamFactory();break;

   }

   ((MainFrame)parent).setView(factory.createView(parent));

}

Podsumowanie

Mam nadzieję, że ten artykuł przekona czytelnika, jak ważne jest poznawanie i wykorzystywanie wzorców projektowych. Mimo że czasem trudne do zrozumienia, są w stanie naprawdę uprościć życie zarówno szaremu programiście, jak i poważnemu projektantowi.


Bibliografia

[1] ErichGamma, Richard Helm, Ralph Johnson, and John Vlissides–„Design Patterns: Elements of Reusable Object-Oriented Software”, 1995

[2] Bruce Eckel, Thinking in Patterns. Problem-Solving Techniques using JavaPresident, MindView, Inc. (www.bruceeckel.com)

[3] Materiały do wykładu Projektowanie Systemów Informatycznych na Politechnice Wrocławskiej.

Załączniki:

Komentarze 0

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.