Monday, October 1, 2007

OJP 1 zajęcia :)

Dziś na zajęciach z OJP usłyszałem pierwszy raz jak wygląda obiektowe programowanie w ujęciu "ewolucji". Powiedzmy, że robimy bazę danych samochodów:



int : rocznik

int : przebieg

string : rejestracja

string : kolor



Operujemy na 4 tablicach - funkcja, która miałaby operować na samochodzie musiałby przyjmować 4 parametry (porażka)... lub działać na zmiennych (tablicach) globalnych. Sortowanie takich samochodów np. według rocznika czy przebiegu sprawia, że drobny błąd w algorytmie (operującym na 4 tablicach) rozsynchronizuje bazę.



Kolejnym pomysłem jest zamknąć te dane w strukturze samochód:



struct samochod {

int : rocznik
int : przebieg
string : rejestracja
string : kolor

}



Dzięki temu funkcja (np. funkcja info() wyświetlająca informację o samochodzie) może przyjmować już tylko jeden parametr (samochód)... Jednak teraz programista może celowo (lub przypadkowo) zmniejszyć przebieg lub zmienić rocznik - czego byśmy nie chcieli. Nasz obiekt nie jest bezpieczny, nie jest niezmienniczy. Nie możemy zagwarantować, że przebieg się nie zmieniszy, że rocznik się nie zmieni.



Idąc dalej możemy zamknąć wszystko w klasie a dane umieścić w sekcji private.



class samochod {



// Interfejs: co sie składa na samochód

private:
int : rocznik

int : przebieg

string : rejestracja

string : kolor

string : numer_silnika

string : marka



public:

// Implementacja: jak się zachowuje samochód

zmien_przebieg() {

++przebieg;

}
}



Dzięki temu programista nie może zmniejszyć przebiegu:



samochod * mazda_jasia = new samochod();

mazda_jasia->przebieg = 200 km;



Dzięki temu jesteśmy pewnie, że przebieg nigdy się nie zwiększy. Minusem tego jest jednak, że nie możemy odczytać przebiegu:



cout <<>przebieg;



No i mamy problem. Musimy więc dopisać nową metodę.



int function Samochod::pobierz_przebieg() {

return this.przebieg;

}



Dzięki temu mamy pełna kontrolę nad przebiegiem. Jesteśmy pewni, że nie zmniejszy się jednak możemy sprawdzić jego wartość i monotonicznie go zwiększać. Jednak jaki rocznik będzie miał samochód, który jest nowo utworzony ? No właśnie. Często zero, jednak nie można na tym polegać... Musimy więc zainicjować wartości na nowo - robimy to w konstruktorze.



Samochod::Samochod() {

this.rocznik = 1970;

this.przebieg = 0;

}



Jednak taki samochód nie ma ani marki ani koloru. Możemy więc zrobić inny konstruktor.



Samochod::Samochod(string _rejestracja, string _silnik, unsigned int _rocznik ...) {

rejestracja = _rejestracja;

silnik = _silnik;

rocznik = _rocznik;

}



Jednak kiepskim pomysłem jest wypisywanie w liście wszystkich parametrów. W powyższym przypadku tworzą się kopie obiektów, które są przekazywane przez do metody. Możemy jednak nie przekazywać parametrów i nie przypisywać ich do skałdowych klasy w ciele konstruktora. Do tego słuzy lista inicjalizacyjna:



Samochod::Samochod(string _rejestracja, string _silnik ...):rejestracja(_rejestracja), silnik(_silnik), kolor("czerwony") {

[...] // tutaj ciało metody

}



Mamy teraz przeciążony konstruktor (istnieją i moga istnieć różne funkcje o tych samych nazwach a o różnych parametrach). Możemy np. zrobić dodatkową metodę zwiększ przebieg:



Samochod::zwieksz_przebieg(int ile) {

if (ile > 0) {

przebieg += ile;

}

}



W C++ mamy możliwość dawać parametrow wartości domyslne, co pozwala uiżytkownikom daje mozliwośc na opuszczanie parametrów. Na z 2 metod zwieksz przebieg możemy zrobić 1:



class {

[...]



zwieksz_przebieg(int ile = 1);

-







-

Samochod::zwieksz_przebieg(int ile) {

}



mazda_jasia->zwieksz_przebieg(); // += 1

mazda_jasia->zwieksz_przebieg(12); // += 12



Wartości domyślne (zmienne z wartościami domyślnymi) dajemy na końcu.



Dalej mówiac o konstruktorze. Jeżeli tworzymy knstruktor dla klasy to musimy zadbać, żeby wszystko było w nim zainicjowane.



Dziedziczenie:

------------------------------------------------



Stosujemy kiedy chcemy stworzyć specjalizację klasy, lub dodać jakieś cechy lub zachowania, których nie miała klasa oryginalna.



-------------

samochod < [ samochod_sportowy ] (bool spoiler, unsigned int data_tuningu)

-------------

^

i a kind of

^

--------------

ciezarowka

--------------



Kiedy zaczniemy dodawac do klas pochodnych coraz wiecej metod czy skladowych pokaze, ze nie warto tworzyc jednej wielkiej klasy samochod z ogromna ilosccia skladowych, poniewaz niektore skladowe (spoilery) dotycza tylko i wylacznie samochodwo sportowych, a w samochodach musialby by zaincjalizowane na jakies dizwne bezsensowen wartosci. Jak wyglada dziedziczenie:



class Ciezarowka public Samochod {



private:

// ciezarowka stala sie samochodem. Dodajemy tylko to co chcemy dodać:

int ladownosc; // Tym się różni od samochodu.

}



Jakie można popełnić błędy ? 1) Otóż mazda nie DZIEDZICZY po samochodzie. Jest samochodem ! a nie klasą... Powinna być obiektem klasy samochód ! a nie samochodem !



2) Kompozycja (is a part of) -> jest częścią. Na przykład silnik jest częścią samochodu, ale nie dziedziczy po samochodzie ! Jest natomiast częścią samochodu ! (diamencik).



3)



elipsa <- punkt : połżenie_środka, float : szerokość, float : wysokość, narysuj()...



Koło nie może dziedziczyć po elpisie ! Koło => (szerokość = wysokość). Wszystko jest ok do momentu, kiedy my nie skomplikujemy elipsy.... np po dodaniu Elipsa::Rozciagnij() { szerokosc *= 2) okazuje się, że koło nie jest dobryn przykładem na dziedziczenie z elipsy... bo koła nie wolno dodawać.



Zasada substytucji Miskowa.



-----------------

Zasłanianie !

-----------------



Samochód: function f(int par), f(string par), f(float par);



class SamochodSportowy public Samochod {



funtion f(string par) {

// Ta metoda zaslania WSZYSTKIE funkcje f z Samochod

}



}



Przeciązanie: w jednej klasie te same nazwy różne parametry

Zasłanianie: w klasie pochodnej metoda o tej samej nazwie.



Dwa sposoby na wybrnięcie z problemu:



1) using Samochod::f; <- pierwszy sposób

2) Wywołać Samochod::f() <- w metodzie klasy.



Projekty ! (wymyślić obiekt do hierarchi dziedziczenia)

No comments: