Obiektowe Projektowanie Aplikacji

Zagadnienie 1

Wstęp do programowania obiektowego w C++ – obiekty, klasy, atrybuty, metody

mgr inż. Grzegorz Kraszewski

Projektowanie obiektowe a język zorientowany obiektowo

Wbrew pozorom projektowanie programu w sposób obiektowy, oraz użycie do programowania języka zorientowanego obiektowo to dwie różne rzeczy. Można projektować obiektowo i zapisać to w języku nie wspierającym obiektowości (np. w języku C), można też pisać nieobiektowe programy w językach obiektowych. Oczywiście, jeżeli już zdecydujemy się na zaprojektowanie programu techniką obiektową, najwygodniej jest wybrać język, który tę obiektowość w naturalny sposób wspiera, na przykład C++.

Nie każde zagadnienie programistyczne kwalifikuje się do zastosowania obiektowego sposobu projektowania. Zdecydowana większość problemów operuje jednak obiektami (zwróćmy uwagę na to słowo) z rzeczywistego życia, bądź ich komputerowymi reprezentacjami. W tej sytuacji „projektowanie obiektowe” polega na przeniesieniu pewnego fragmentu rzeczywistości do wnętrza komputera i zapisaniu tego w języku programowania. Oczywiście współczesne komputery nie są zdolne do przeniesienia do nich nawet małego fragmentu rzeczywistego świata wraz z najdrobniejszymi szczegółami. Najczęściej nie jest to zresztą wcale potrzebne. Najtrudniejszymi decyzjami jakie podejmuje programista są właśnie decyzje na etapie projektowania dotyczące poziomu szczegółowości „symulacji” rzeczywistości w komputerze i sposobu odzwierciedlenia pojęć rzeczywistych pojęciami, jakimi operuje język programowania. Są to decyzje tym trudniejsze, że błędy popełnione na tym etapie są trudne do naprawienia i często kończą się tym, że cały projekt trzeba zacząć od nowa. Obiekt, klasa, atrybut, metoda – te cztery słowa, to fundamentalne pojęcia projektowania obiektowego. Z reguły najłatwiej zacząć od wyodrębnienia w zagadnieniu występujących w nim obiektów. Następnie, poszukując cech wspólnych i uogólniając opisy, dochodzimy do pojęcia klasy. Następnie możemy wypełnić klasę szczegółami, a więc atrybutami (cechami) obiektów oraz metodami, za pomocą których manipulujemy obiektami i wpływamy na ich stan. Sam proces projektowania nie wymaga z definicji żadnych sformalizowanych narzędzi (często za narzędzia wystarczą kartka i długopis...), aczkolwiek specjalne zapisy w rodzaju UML są pomocne w przypadku dużych programów, szczególnie pisanych zespołowo. Aby dłużej nie teoretyzować, przejdźmy do przykładu, który jest tematem dzisiejszego zadania (opis zadania poniżej). Zaczynamy od wyodrębnienia obiektów, jakie występują w zadaniu.

Obiekt

Ponieważ zadanie jest opisane w miarę precyzyjnie, nie powinno być z tym problemu, obiektami są: centrala, regały, półki i moduły. Chwili zastanowienia wymaga pytanie, czy slot (miejsce na moduł) jest również obiektem. „Jedynie słusznej” odpowiedzi nie ma, można napisać program w którym sloty jako obiekty występują, albo nie. To przykład na to, że projektowanie programów jest po części sztuką i pewną rolę odgrywa tu intuicja projektanta podbudowana doświadczeniem. W złożonych przypadkach pomóc też może formalna analiza zależności między obiektami. Generalnie mnożenie rodzajów obiektów jest korzystne z punktu widzenia elastyczności programu i podatności na przyszłe modyfikacje. Z drugiej jednak strony prowadzi to do skomplikowania budowy programu i skutkuje większym i wolniejszym kodem wynikowym.

Klasa

Kolejnym krokiem po określeniu obiektów jest opisanie ich klas. Często jest to etap nierozerwalnie połączony z poprzednim. Tak jest i w naszym przykładzie. Określając obiekty określiliśmy jednocześnie ich klasy. W programie będziemy mieli obiekt klasy Centrala (to dość szczególna klasa, bo w programie zawsze wystąpi tylko jeden obiekt tej klasy), następne klasy to Regał, Półka, Slot i Moduł. W bardziej złożonych projektach dochodzi do tego etapu analiza zależności między klasami i stworzenie drzewa dziedziczenia klas. Temu zagadnieniu będzie poświęcone jedno z kolejnych ćwiczeń. Nasz przykład ćwiczeniowy został zdefiniowany w taki sposób, żeby można go było zrealizować w sposób naturalny bez korzystania z dziedziczenia klas. Przypomnę, że w języku C++ klasę zapisuje się w następujący sposób:
class Centrala
{
  /* tu znajdują się atrybuty i metody */
};

Atrybuty i elementy

Atrybuty to innymi słowy cechy obiektów. Wszystkie obiekty należące do danej klasy mają identyczny zestaw atrybutów, choć oczywiście wartości tych atrybutów mogą być różne dla każdego obiektu. Atrybuty obiektów w języku C++ reprezentujemy poprzez składowe klasy. Składowa klasy jest zmienną dowolnego typu języka. Każdy obiekt posiada swoją własną kopię tej zmiennej. Identycznie pod względem zapisu w języku C++ zachowują się elementy obiektu, różne jest ich znaczenie z punktu widzenia programisty. Nie opisują one bowiem cech obiektu, a podobiekty jakie w sobie zawiera. Oczywiście podobiekty te wyszczególniamy jedynie wtedy, gdy interesują nas z punktu widzenia programu. Na przykład obiekt klasy Centrala zawiera podobiekty klasy Regał. Obiekt klasy Regał zawiera podobiekty klasy Półka, a Półka posiada podobiekty klasy Slot. Z kolei obiekt klasy Moduł mógłby posiadać podobiekty opisujące elementy elektroniczne na płytce modułu. Jednak taki poziom szczegółowości jest zbędny z punktu widzenia programu i niepotrzebnie by go skomplikował. Zbyt dokładne modelowanie rzeczywistości w programie jest również błędem... Oto prrzykładowy zapis atrybutów obiektu, oraz elementów składowych:
class Modul
{
  int Wlaczony;  /* cechy modułu */
  int Typ;
};


class Centrala
{
  Regal Regaly[3];  /* elementy składowe - regały, umieszczone w tablicy */
};

Metody

Metody to najprościej mówiąc czynności, jakie możemy wykonać na obiekcie. Z punktu widzenia składni języka C++, metody to funkcje zadeklarowane (a być może również zdefiniowane) wewnątrz definicji klasy. Niejawnym domyślnym parametrem metody jest obiekt, na którym metoda jest wykonywana. Do składowych (atrybutów, podobiektów i innych metod) tego obiektu możemy się odwoływać przez proste podanie ich nazwy. Metoda może zostać zadeklarowana i zdefiniowana na dwa sposoby. W pierwszym zarówno deklarację, jak i treść (definicję) metody umieszczamy wewnątrz definicji klasy. W drugim deklaracja znajduje się w klasie, a definicja na zewnątrz. Oto przykład ilustrujący oba sposoby.
class Modul
{
  int Wlaczony;  /* cechy modułu */
  int Typ;

  public:

  void Wlacz() { Wlaczony = 1; }
  void Wylacz();
};

void Modul::Wylacz() { Wlaczony = 0; }
Różnica między tymi dwiema definicjami polega na sposobie generowania kodu przez kompilator dla wywołań obu metod. Jeżeli treść metody umieścimy wewnątrz klasy, to metoda jest traktowana przez kompilator jako funkcja inline. Polega to na tym, że w miejscu każdego wywołania funkcji wstawiany jest cały jej kod. Oznacza to, że kod funkcji powtórzony jest w programie tyle razy, w ilu miejscach jest ona wywoływana. Drugi sposób zdefiniowania metody (definicja na zewnątrz) zaleca kompilatorowi traktowanie metody jako zwykłej funkcji. Jej kod umieszczany jest w programie raz, a we wszystkich miejscach wywołania umieszczane są instrukcje skoku. Warto zauważyć że w definicji metody poza klasą musimy stosować operator zakresu nazwy (podwójny dwukropek) poprzedzony nazwą klasy, aby kompilator wiedział, że to metoda klasy Modul a nie jakaś zwykła globalna funkcja. Jak i kiedy stosować obie postacie definiowania metod? Generalnie najlepiej posługiwać się sposobem drugim, w ten sposób skompilowany program jest krótszy. Sposób pierwszy stosujemy w następujących przypadkach:

Zadanie do wykonania

Zadanie polega na zaprojektowaniu i napisaniu programu opisującego cyfrową centralę telefoniczną (np. centralę Alcatel S12 znajdującą się w sali WA-039) jako zbiór modułów o różnych nazwach, które mogą być umieszczane i wyjmowane w slotach zgrupowanych w półki, które z kolei znajdują się w regałach. Zakładamy, że moduł cechuje się typem i stanem (włączony/wyłączony) oraz miejscem umieszczenia w centrali (regał, półka, slot). Typ modułu nie jest dowolny, ale wybrany spośród określonego zestawu typów, których powinno być 10. Z każdym typem powinna być związana nazwa (dobór nazw pozostawiam fantazji wykonujących ćwiczenie). Program powinien umożliwiać następujące czynności: