11 października 2008

Fizyczność logiki

Projektowanie kodu aplikacji jest z pewnością procesem niezwykle złożonym i pracochłonnym. Aplikacja cechująca się wysoka jakością, nie ogranicza się tylko do odpowiedniej funkcjonalności, stabilności i wydajności. Niezwykle ważną cechą jest także możliwość łatwej konserwacji a także ponownego użycia niektórych jej elementów. Oczywiście z pomocą przychodzi nam projektowanie zorientowane obiektowo. Podział kodu na logiczne części (klasy) i opisanie ich poziomu abstrakcji znacznie zwiększa czytelność projektu. Dodatkowo, do dyspozycji mamy też wyspecjalizowane narzędzia temu służące, jak np. UML.

Projekt programu może być dwojaki: logiczny lub fizyczny. Projekt logiczny opisuje problemy występujące na poziomie implementacji. Decyzja o tym, czy dany operator będzie członkiem klasy lub czy dana klasa powinna mieć konstruktor domyślny, zapada na etapie projektu logicznego. Projekt fizyczny zaś, określa zależności pomiędzy poszczególnymi komponentami programu. Wbrew pozorom, obydwa rodzaje są ze sobą ściśle powiązane i często nieświadomie projektujemy aplikacje od strony fizycznej.

Podstawowym elementem projektu fizycznego jest komponent, czyli spójna i niezależna jednostka będąca zazwyczaj klasą (lub zbiorem klas określonej funkcjonalności) zdefiniowaną w pliku nagłówkowym i zaimplementowaną w pliku cpp. Załóżmy, że tworzymy klasę przechowującą punkty na płaszczyźnie dwuwymiarowej. Klasa ta udostępnia pewną podstawową funkcjonalność (dodawanie, usuwanie,sortowanie punktów). Interesuje nas także możliwość przeglądania listy punktów, jednak przy wykorzystaniu iteratora. Tworzona przez nas klasa iteratora powinna znajdować się w tych samych plikach co główna klasa będąca kontenerem punktów. Fizycznie (a także logicznie) stanowią one jeden komponent udostępniający określoną z góry funkcjonalność (przechowywanie i przeglądanie danych).

Pomiędzy komponentami mogą występować określone relacje:

  • Jest
  • Używa-W-Interfejsie
  • Używa-W-Implementacji

Relacja Jest bezpośrednio wynika z dziedziczenia. Klasa A jest Klasą B, jeśli klasa A publicznie dziedziczy po klasie B. Używa-W-Interfejsie oznacza, że dany typ jest wykorzystywany w interfejsie danej klasy. Kiedy tak się dzieje? Gdy typ jest zwracany przez jedną z metod klasy, przyjmowany na liście argumentów lub znajduje się w sekcji prywatnej. Ogólnie rozróżnia się dwa przypadki tej zależności: pełna i z nazwy. Przypadek pierwszy, mówi o tym, że do kompilacji klasy wymagana jest znajomość całej definicji obiektu (dołączenie jej za pomocą #include). Jest to najczęściej spotykany przypadek. Zależność Tylko-Z-Nazwy jest wykorzystywana tam, gdzie mamy do czynienia ze wskaźnikami lub referencjami na obiekt. Wtedy potrzebujemy tylko nazwy obiektu - korzystamy z deklaracji wyprzedzającej. Ostatnia z relacji, czyli Używa-W-Implementacji, jak sama nazwa wskazuje, mówi o tym, że dany typ używany jest w implementacji funkcjonalności klasy. Wszystkie pliki nagłówkowe, niezbędne do kompilacji, należy dodać poleceniem #include w pliku cpp klasy (nie w pliku nagłówkowym). Odpowiednie wykorzystanie wyżej zaprezentowanych relacji pozwoli nam na bardziej przejrzyste zaprojektowanie naszej aplikacji, a także skrócenie czasu kompilacji lub nawet linkowania.

2 komentarze:

  1. Coś mi tu nie gra. Mnie uczyli, że projekt logiczny to jest taki bardziej na poziomie abstrakcyjnym, a projekt fizyczny zajmuje się szczegółami implementacji, czyli jak to zrealizować w konkretnym języku programowania.

    OdpowiedzUsuń
  2. No to dziwnie, bo w takiej "C++ Projektowanie Systemów Informtycznych" piszą inaczej. Z resztą, może wyrażenia "projekt logiczny\fizyczny" były użyte w innym niż tu kontekscie?

    OdpowiedzUsuń