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:
- metoda jest bardzo krótka, kompiluje się do kilku instrukcji asemblera,
 
- metoda jest dość krótka i bardzo intensywnie wywoływana, tak że instrukcja skoku i przekazanie
parametrów istotnie spowalnia program.
 
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:
- 
Umieszczenie modułu wybranego typu w pustym slocie o wskazanym położeniu.
 
- 
Usunięcie modułu ze wskazanego slotu, ale pod warunkiem, że moduł jest wyłączony.
 
- 
Włączenie i wyłączenie modułu we wskazanym slocie.
 
- 
Wypisanie listy modułów danego typu z wyświetleniem nazwy, położenia i stanu.
 
- 
Wypisanie listy wszystkich modułów, wszystkich włączonych modułów, wszystkich wyłączonych modułów
dla: całej centrali, wybranego regału, wybranej półki.
 
- 
Zapisanie stanu centrali do pliku na dysku, załadowanie stanu centrali z dysku.