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;
}