Projektowanie klas
Transkrypt
Projektowanie klas
Projektowanie klas – c.d. ogólne wskazówki dotyczące projektowania klas: o wyodrębnienie klasy – odpowiedź na potrzeby życia (obsługa rozwiązania konkretnego problemu) o zwykle nie uda się utworzyć idealnej definicji – jej precyzowanie następuje w trakcie pracy o zachowuj prostotę – zaczynaj od małych i prostych obiektów o oczywistym przeznaczeniu o zacznij programować – sprawdź w działaniu proponowany projekt Projektowanie klas – przykład cel: napisanie programu ułatwiającego zarządzaniem portfelem papierów wartościowych pytanie – jak reprezentować takie papiery – co ma być podstawowym obiektem opisu; możliwości: o obiekt reprezentuje pojedynczy udział o obiekt – zapis puli udziałów jednej spółki przez będących w posiadaniu jednej osoby wybór drugiej możliwości – klasa Akcje; operacje wykonywane na udziałach: o nabycie udziałów nowej spółki o sprzedaż pewnej ilości udziałów o zakup pewnej ilości udziałów o aktualizacja wartości pojedynczego udziału o wyświetlenie informacji o posiadanych udziałach powyższa lista określa podstawowy publiczny interfejs klasy Akcje lista informacji, które powinny zawierać obiekty klasy o nazwa spółki o liczba posiadanych udziałów o wartość jednostkowa udziału o łączna wartość akcji danej spółki w posiadaniu danej osoby deklaracje klasy Akcje: class Akcje // deklaracja klasy { private: char firma[30]; int udzialy; double cena; double wart_calk; void ustal_wart() { wart_calk = udzialy * cena; } public: void nabycie(const char* co,int ile, double po_ile); void sprzedaj(int ile, double po_ile); void kupuj(int ile, double po_ile); void aktualizyj(double nowa_cena); void pokaz(); }; cechy deklaracji klasy: o dane ukryte, niedostępne poza klasą o publiczne funkcje składowe – pośrednik między programem a obiektem klasy (publiczny interfejs) o oddzielenie publicznego interfejsu od szczegółów implementacji (zazwyczaj implementacja metod klasy – poza ciałem klasy, w oddzielnym pliku) o ukrywanie danych – zwolnienie użytkowników od konieczności znajomości wewnętrznej reprezentacji danych o prywatna metoda klasy : ustal_wart() Implementacja metod: void Akcje::nabycie(const char * co, int ile, double po_ile) { strncpy(firma, co, 29); firma[29] = '\0'; if (ile < 0) { std::cerr << "Liczba udzialow nie moze byc ujemna; " << "ustalam liczbe udzialow " << firma << " na 0.\n"; udzialy = 0; } else udzialy = ile; cena = po_ile; ustal_wart(); } void Akcje::kupuj(int ile, double po_ile) { if (ile < 0) { std::cerr << "Liczba nabywanych udzialow nie moze byc ujemna " << "Transakcja przerwana.\n"; } else { udzialy += ile; cena = po_ile; ustal_wart(); } } void Akcje::sprzedaj(int ile, double po_ile) { using std::cerr; if (ile < 0) { cerr << "Liczba sprzedawanych udzialow nie moze byc ujemna. " << "Transakcja przerwana.\n"; } else if (ile > udzialy) { cerr << "Nie mozesz sprzedac wiecej udzialow, niz posiadasz! " << "Transakcja przerwana.\n"; } else { udzialy-= ile; cena = po_ile; ustal_wart(); } } void Akcje::aktualizuj(double pr) { cena = pr; ustal_wart(); } void Akcje::pokaz() { using std::cout; using std::endl; cout << "Spolka: " << firma << " Liczba udzialow: " << udzialy << endl << " Cena udzialu: " << cena << " zl" << " Laczna wartosc udzialow: " << wart_calk << " zl" << endl; } analiza działania programu wykorzystującego działanie klasy Akcje: Akcje p1; p1.kupuj(10,15.0); p1.pokaz; Spolka: ÎĂÁw_˙" Liczba udzialow: 2009249824 Cena udzialu: 3.7 zl Laczna wartosc udzialow: 7.43422e+009 zl problem – inicjalizacja zmienych – możliwa tylko w funkcji nabycie; jeżeli nie jest to pierwsza operacja na zmiennej, to mamy problem rozwiązanie – zastąpienie funkcji nabycie konstruktorem inne ulepszenia o wykorzystanie klasy string do reprezentacji łańcucha znaków o funkcje składowe zależne od dwóch obiektów o wykorzystanie tablic obiektów o struktura plików – definicji i implementacji klasy, plików zastosowań o wykorzystanie stałych o zasięgu klasy o separacja interfejs - implementacja problem inicjalizacji – wprowadzamy konstruktory o konstruktor zastępujący funkcję nabycie o konstruktor domyślny class Akcje { ... public: Akcje(const char* co,int ile=0, double po_ile=0.0); Akcje( ); }; w poprzedniej wersji – pole firma: C-string o określonej długości o problem – ograniczenie na ilość znaków – obcięcia długości albo niewykorzystanie miejsca o rozwiązanie – zastosowanie klasy string class Akcje { private: std:string firma; ... }; problem – mamy kilka obiektów typu Akcje – chcemy porównać dowolną ich parę by określić, który z nich ma większą wartość rynkową rozwiązanie – funkcja która zależy od dwóch obiektów podlegających porównaniu Akcje drozsza(Akcje & o1, Akcje & o2) problem dostępu do danych – najlepiej, by była to funkcja składowa klasy Akcje wywołanie takich funkcji – zawsze na rzecz obiektu klasy: składnia takiego wywołania: Akcje o1,o2,w; w=o1.drozsza(o2); lub w=o2.drozsza(o1); jak przekazać odpowiedź funkcji drozsza ? o najlepiej – przekazać referencję obiektu który reprezentuje większą wartość udziałów, to sugeruje prototyp funkcji postaci: const Akcje & drozsza(const Akcje & druga) const; o funkcja korzysta z dwóch obiektów – jawnie z tego, który jest jej argumentem (druga), niejawnie z tego, na rzecz którego została wywołana o jak zwrócić referencję obiektu, na rzecz którego funkcja jest wywołana? o rola wskaźnika this const Akcje & drozsza(const Akcje & druga) { if(druga.wart_calk>= wart_calk) return druga; else return *this; } zapis klasy – dwa pliki: o plik nagłówkowy klasy // plik Akcje.h - deklaracja klasy #ifndef AKCJE_h_ #define AKCJE_h_ class Akcje // deklaracja klasy { private: static const int ROZMIAR = 30; std::string firma; int udzialy; double cena; double wart_calk; void ustal_wart() { wart_calk = udzialy * cena; } public: Akcje( ); Akcje(const char* co,int ile=0, double po_ile=0.0); const Akcje & drozsza(const Akcje & druga); void kupuj(int ile, double po_ile); void sprzedaj(int ile, double po_ile); void aktualizuj(double nowa_cena); void pokaz(); void pokaz_bez_0(); }; #endif o plik z implementacją funkcji składowych: #include <iostream> #include <string> #include "akcje.h" using namespace std; Akcje::Akcje(const char * co, int ile, double po_ile) { firma=co; if (ile < 0) { cerr << "Liczba udzialow nie moze byc ujemna; " << "ustalam liczbe udzialow w " << firma << " na 0.\n"; udzialy = 0; } else udzialy = ile; cena = po_ile; ustal_wart(); } Akcje::Akcje( ) { firma="brak nazwy"; udzialy=0; cena = 0.0; ustal_wart(); } const Akcje & Akcje::drozsza(const Akcje & druga) { if(druga.wart_calk>= wart_calk) return druga; else return *this; } void Akcje::kupuj(int ile, double po_ile) { if (ile < 0) { cerr << "Liczba nabywanych udzialow nie moze byc ujemna. " << "Transakcja przerwana.\n"; } else { udzialy += ile; cena = po_ile; ustal_wart(); } } void Akcje::sprzedaj(int ile, double po_ile) { using std::cerr; if (ile < 0) { cerr << "Liczba sprzedawanych udzialow nie moze byc ujemna. " << "Transakcja przerwana.\n"; } else if (ile > udzialy) { cerr << "Nie mozesz sprzedac wiecej udzialow, niz posiadasz! " << "Transakcja przerwana.\n"; } else { udzialy-= ile; cena = po_ile; ustal_wart(); } } void Akcje::aktualizuj(double po_ile) { cena = po_ile; ustal_wart(); } void Akcje::pokaz() { string nazwa(firma); nazwa.resize(ROZMIAR); string ss(ROZMIAR+30,'-'); cout << ss << "\n" <<"Spolka: " << nazwa << " Liczba udzialow: " << udzialy << endl << " Cena udzialu: " << cena << " zl" << " Laczna wartosc udzialow: " << wart_calk << " zl" << endl; } void Akcje::pokaz_bez_0( ) { if(wart_calk > 0.0) pokaz( ); } o funkcja pokaz( ) – obcięcie i normalizacja długości nazwy firmy w wydruku o funkcja pokaz – prezentuje tylko obiekty o całkowitej wartości > 0 o wykorzystanie stałej o zasięgu klasy ROZMIAR plik klienta - wykorzystanie definicji klasy o włączenie nagłówka i pliku implementacji klasy o zastosowanie tablicy o inicjalizacja tablic obiektów – typowa składnia inicjalizacja tablicy: ujęta w nawiasy klamrowe lista wyrażeń inicjalizujących (wywołań konstruktorów dla poszczególnych elementów tablicy) o użycie konstruktora domyślnego #include "akcje.cpp" #include "akcje.h" int main() { const int ROZ_PORTFEL = 10; Akcje portfel[ROZ_PORTFEL] = { Akcje("KGHM", 70,136.6), Akcje("Zelmer", 300, 27.0), Akcje("Comarch", 60,57.10) }; cout << "Sklad portfela: \n"; cout.setf(ios_base::fixed); // format #.## cout.precision(2); // format #.## cout.setf(ios_base::showpoint); // format #.## for(int st=0; st < ROZ_PORTFEL; st++) portfel[st].pokaz_bez_0(); // tylko akcje o wartości>0 portfel[1].kupuj(245, 33.10); Akcje best=portfel[0]; for(int st=0; st < ROZ_PORTFEL; st++) best=best.drozsza(portfel[st]); cout << "\n\n\nNajcenniejszy walor:\n"; best.pokaz(); } Projektowanie klas – interfejs, implementacja publiczne składowe klasy – interfejs klasy zmiany w interfejsie – zmieniają sposób działania klasy, listę dostępnych działań prywatne składowe klasy oraz definicje funkcji składowych – implementacja klasy zmiany w implementacji – wpływają na wewnętrzne szczegóły działania klasy, nie wpływają na sposób wykorzystania klasy Abstrakcyjne typy danych opis typu danych dokonany w sposób uogólniony (bez uwarunkowań języka programowania i szczegółów implementacji przykład - stos wymagane własności: o zdolność do przechowania pewnej ilości elementów (rodzaj kontenera) o możliwość wykonania operacji: tworzenie pustego stosu odłożenie elementu na stos zdjęcie elementu ze stosu sprawdzenie, czy stos jest pusty sprawdzenie, czy stos jest pełny realizacja – deklaracja klasy o operacje – publiczne funkcje składowe (interfejs) o dane stosu – przechowywane w prywatnych danych klasy o swoboda w wyborze pojemnika danych (tablica statyczna, tablica alokowana dynamicznie, lista) o interfejs przesłania dokładną reprezentację wewnętrzną stosu o nasz wybór: statyczna tablica, ustalony rozmiar maksymalny, stan wypełnienia – indeks elementu szczytowego stosu o staramy się uzyskać niezależność od typu zawartości stosu definicja stosu – plik nagłówkowy // definicja klasy stos #ifndef STOS_H_ #define STOS_H_ typedef unsigned int Item; class Stos { enum {MAX = 10}; Item items[MAX]; int top; // rozmiar maksymalny // tablica przechowująca elementy // indeks elementu na szczycie public: Stos(); bool jest_pusty( ) const; bool jest_pelny( ) const; bool poloz(const Item & item); bool zdejm(Item & item); }; #endif definicja stosu – implementacja metod #include "stos.h" typedef unsigned int Item; Stos::Stos() { top=0; } //tworzy pusty stos bool Stos::jest_pusty() const { return top==0; //pusty, gdy indeks szczytu ma wartosc 0 } bool Stos::jest_pelny() const { return top==MAX; //pełny, gdy indeks szczytu = MAX } bool Stos::poloz(const Item & item) { if(top<MAX) //mozliwe, gdy indeks szczytu < MAX { items[top++]=item; //wpisuje wartość elementu return true; //zwraca TRUE } else return false; // stos pełny, nic nie robimy } bool Stos::zdejm(Item & item) { if(top>0) //możliwe, gdy stos niepusty { item=items[--top]; //zwracamy element stosu przez argument return true; // O.K., udało się } else return false; //stos pusty, nic nie robimy } interfejs publiczny przesłania sposób implementacji stosu (możliwa zmiana implementacji bez zmiany sposobu korzystania z klasy sposób definicji stosu nie zależy od typu danych w nim przechowywanych – by go zmienić wystarczy modyfikacja instrukcji typedef definicja stosu – implementacja metod Przykład wykorzystania: #include <iostream> #include "stos.h" #include "stos.cpp" using namespace std; int main(int argc, char *argv[]) { Stos karty; Item pp; char z; cout << "Nacisnij D by wczytac nastepna karte \n" << "P by przetworzyc karte, lub E by zakonczyc.\n"; while(cin >>z && toupper(z) != 'E') { while(cin.get() != '\n') continue; if(!isalpha(z)) { cout << '\a'; continue; } switch(toupper(z)) { case 'D': cout << "Podaj nr albumu: "; cin >> pp; if(karty.jest_pelny()) cout << "Stos pelny!\n"; else karty.poloz(pp); break; case 'P': if (karty.jest_pusty()) cout << "Stos pusty!\n"; else { karty.zdejm(pp); cout<< "Karta nr " << pp << " zdjeta\n"; } break; } cout << "Nacisnij D aby wczytac nastepna karte \n" << "P by przetworzyc karte, lub E by zakonczyc.\n"; } cout << "No to tyle na dzis !!!\n"; system("PAUSE"); return EXIT_SUCCESS; }