min prog v 1 0 (2)


Programowanie obiektowe
Wstęp
Programowanie obiektowe (ang. object-oriented programming)  metodyka
tworzenia programów komputerowych, która definiuje programy za pomocą obiektów 
elementów łączących stan (czyli dane) i zachowanie (czyli procedury, tu: metody).
Obiektowy program komputerowy wyrażony jest jako zbiór takich obiektów,
komunikujących się pomiędzy sobą w celu wykonywania zadań.
Podejście to różni się od tradycyjnego programowania proceduralnego, gdzie dane i
procedury nie są ze sobą bezpośrednio związane. Programowanie obiektowe ma ułatwić
pisanie, konserwację i wielokrotne użycie programów lub ich fragmentów.
Programowanie obiektowe a rzeczywistość
Formie w programowaniu obiektowym odpowiada Klasa, materii - instancja -
obiekt. Jest to najbardziej naturalny sposób rozumienia rzeczywistości - podstawową
cechą mózgu ludzkiego jest klasyfikacja - łączenie występujących w rzeczywistości
obiektów w grupy - klasy.
Podstawowe założenia paradygmatu obiektowego
Cechy języków programowania, które czynią je obiektowymi:
Abstrakcja - Każdy obiekt w systemie służy jako model abstrakcyjnego "wykonawcy",
który może wykonywać pracę, opisywać i zmieniać swój stan, oraz komunikować się z
innymi obiektami w systemie, bez ujawniania, w jaki sposób zaimplementowano dane
cechy.
Enkapsulacja - Czyli ukrywanie implementacji, hermetyzacja. Zapewnia, że obiekt nie
może zmieniać stanu wewnętrznego innych obiektów w nieoczekiwany sposób. Tylko
wewnętrzne metody obiektu są uprawnione do zmiany jego stanu.
Polimorfizm - Referencje i kolekcje obiektów mogą dotyczyć obiektów różnego typu, a
wywołanie metody dla referencji spowoduje zachowanie odpowiednie dla pełnego typu
obiektu wywoływanego. Jeśli dzieje się to w czasie działania programu, to nazywa się to
póznym wiązaniem lub wiązaniem dynamicznym. Niektóre języki udostępniają bardziej
statyczne (w trakcie kompilacji) rozwiązania polimorfizmu - na przykład szablony i
przeciążanie operatorów w C++.
Dziedziczenie - Porządkuje i wspomaga polimorfizm i enkapsulację dzięki umożliwieniu
definiowania i tworzenia specjalizowanych obiektów na podstawie bardziej ogólnych.
Historia programowania obiektowego
W punktach  kto wymyślił, pierwsze języki i terazniejsze.
 pierwotna koncepcja programowania obiektowego: Simuli 67, (Ole-Johana Dahla i
Kristena Nygaarda z Norsk Regnesentral w Oslo
 kolejna koncepcja została dopracowana w języku Smalltalk, stworzonym w Simuli w
Xerox PARC,
 Programowanie obiektowe zyskało status techniki dominującej w połowie lat 80.,
głównie ze względu na wpływ C++, w tym okresie cechy obiektowe dodano do wielu
języków programowania, w tym Ady, BASIC-a, Lisp-a, Pascala i innych.
 Eiffel Bertranda Meyera był wczesnym przykładem w miarę udanego języka
spełniającego te założenia; obecnie został on w zasadzie całkowicie zastąpiony przez
Javę, głównie za sprawą pojawienia się Internetu,
1. Pojęcia klasy i obiektu. Przykład klasy i kilku obiektów tej klasy.
Obiekt to podstawowe pojęcie wchodzące w skład paradygmatu obiektowości w analizie i
projektowaniu oprogramowania oraz w programowaniu.
Klasa to zbiór obiektów wyróżniających się wspólnymi cechami takimi jak struktura i
zachowanie; częściowa lub całkowita definicja dla obiektów.
Obiekt jest to struktura zawierająca:
" dane
" metody, czyli funkcje służące do wykonywania na tych danych określonych zadań.
Z reguły obiekty (a właściwie klasy, do których te obiekty należą) są konstruowane tak,
aby dane przez nie przenoszone były dostępne wyłącznie przez odpowiednie metody, co
zabezpiecza je przed niechcianymi modyfikacjami. Takie zamknięcie danych nazywa się
enkapsulacją czyli jakby zamknięcie ich w kapsule.
W istocie obiekty są rozwinięciem koncepcji programowania z kontrolą typów zmiennych.
W programowaniu obiektowym obiekty tworzone są dynamicznie jako podstawowy
element konstrukcji programu.
Każdy obiekt ma trzy cechy:
" tożsamość, czyli cechę umożliwiającą jego identyfikację i odróżnienie od innych
obiektów;
" stan, czyli aktualny stan danych składowych;
" zachowanie (ang. behaviour), czyli zestaw metod wykonujących operacje na tych
danych.
2 . Dzi edzi cz e ni e . Przykł ad hi er ar chii kl a s .
Dziedziczenie (ang. inheritance) to w programowaniu obiektowym operacja polegająca
na stworzeniu nowej klasy na bazie klasy już istniejącej.
Na przykład, jeśli mamy klasę (w C++):
class Punkt {
public:
float x,y;
Punkt(float _x, float _y);
virtual void wypisz();
virtual void przesuń(float przesuniecie_x, float
przesuniecie_y);
};
Użycie dodatkowej klasy, która różni się od tej jedynie w kilku szczegółach-> wykorzystane
dziedziczenie:
Dziedziczona klasa może zostać zdefiniowana w następujący sposób:
class NazwanyPunkt: public Punkt { //lista pochodzenia
public:
string nazwa;
NazwanyPunkt(float _x=0,float _y=0,string _nazwa=NULL);
virtual void wypisz();
};
Nowa klasa o nazwie NazwanyPunkt wywodzi się od klasy Punkt (klasa podstawowa,
nadklasa, rodzic).
W C++ klasie pochodnej możemy zdefiniować:
" dodatkowe dane składowe (w naszym przykładzie: string nazwa;)
" dodatkowe funkcje składowe (w naszym przykładzie zmieniliśmy funkcję wypisz())
" nową treść funkcji wirtualnej
W innych językach szczegóły dziedziczenia mogą wyglądać odmiennie, np. w CLOS klasa
pochodna może wpływać na metody odziedziczone po klasie podstawowej, ogólna zasada
dziedziczenia pozostaje jednak taka sama.
Dziedziczenie wielokrotne (ang. multiple inheritance) nazywane także dziedziczeniem
wielobazowym to operacja polegająca na dziedziczeniu po więcej niż jednej klasie
bazowej. Dziedziczenie wielokrotne stosowane jest np. w języku C++:
class Samochod {
public:
int iloscKol;
void jedz() { /* ciało metody */ }
};
class Lodz {
public:
float wypornosc;
void plyn() { /* ciało metody */ }
};
class Amfibia : public Samochod , public Lodz {
public:
string nrRejestracyjny;
};
W efekcie wielokrotnego dziedziczenia Klasa Amfibia posiada wszystkie pola i metody
swoich klas bazowych.
...
void napompujKolo( Samochod& samochod ) { /* ciało metody */ }
void naprawKadlub( Lodz& lodz ) { /* ciało metody */ }
int main() {
Amfibia amfibia;
napompujKolo( amfibia );
naprawKadlub( amfibia );
return 0;
}
Widzimy, że obiekt amfibia jest jednocześnie typu Samochod i Lodz.
Wielokrotne dziedziczenie a interfejsy
Zarówno dziedziczenie wielokrotne, jak i interfejsy pozwalają na uzyskanie
równoważnego efektu - możliwości traktowania obiektu polimorficznie ze względu na
wiele, niespokrewnionych ze sobą typów.
W przypadku użycia interfejsów, czynności dziedziczenia (współdzielenia
implementacji) i dzielenia interfejsu (czyli zewnętrznego kontraktu) są celowo rozdzielone.
3 . Metody wir tu aln e . Przykł ad ilu s trując y ich użyt e c zn ość.
Przykład w C++
#include
const float pi = 3.14159;
class Figura {
public:
virtual float pole() const { // deklaracja metody wirtualnej
return -1.0;
}
};
class Kwadrat : public Figura {
public:
Kwadrat( const float bok ) : a( bok ) {}
float pole() const {
return a * a;
}
private:
float a; // bok kwadratu
};
class Kolo : public Figura {
public:
Kolo( const float promien ) : r( promien ) {}
float pole() const {
return pi * r * r;
}
private:
float r; // promien kola
};
void wyswietlPole( Figura& figura ) {
std::cout << figura.pole() << endl;
return;
}
int main() {
// deklaracje obiektow:
Figura jakasFigura;
Kwadrat jakisKwadrat( 5 );
Kolo jakiesKolo( 3 );
Figura* wskJakasFigura = 0; // deklaracja wskaznika
// obiekty -------------------------------
std::cout << jakasFigura.pole() << endl; // wynik: -1
std::cout << jakisKwadrat.pole() << endl; // wynik: 25
std::cout << jakiesKolo.pole() << endl; // wynik: 28.274...
// wskazniki -----------------------------
wskJakasFigura = &jakasFigura;
std::cout << wskJakasFigura->pole() << endl; // wynik: -1
wskJakasFigura = &jakisKwadrat;
std::cout << wskJakasFigura->pole() << endl; // wynik: 25
wskJakasFigura = &jakiesKolo;
std::cout << wskJakasFigura->pole() << endl; // wynik: 28.274...
// referencje -----------------------------
wyswietlPole( jakasFigura ); // wynik: -1
wyswietlPole( jakisKwadrat ); // wynik: 25
wyswietlPole( jakiesKolo ); // wynik: 28.274...
return 0;
}
Wywołanie metod składowych dla każdego z obiektów powoduje wykonanie metody
odpowiedniej dla klasy danego obiektu. Następnie wskaznikowi wskJakasFigura zostaje
przypisany adres obiektu jakasFigura i zostaje wywołana metoda float pole().
Wynikiem jest "-1" zgodnie z treścią metody float pole() w klasie Figura. Następnie
przypisujemy wskaznikowi adres obiektu klasy Kwadrat - możemy tak zrobić ponieważ
klasa Kwadrat jest klasą pochodną od klasy Figura - jest to tzw. rzutowanie w górę.
Wywołanie teraz metody float pole() dla wskaznika nie spowoduje wykonania metody
zgodnej z typem wskaznika - który jest typu Figura* lecz zgodnie z aktualnie
wskazywanym obiektem, a więc wykonana zostanie metoda float pole() z klasy
Kwadrat (gdyż ostatnie przypisanie wskaznikowi wartości przypisywało mu adres obiektu
klasy Kwadrat). Analogiczna sytuacja dzieje się gdy przypiszemy wskaznikowi adres
obiektu klasy Kolo. Następnie zostaje wykonana funkcja void
wyswietlPole(Figura&) która przyjmuje jako parametr obiekt klasy Figura przez
referencję. Tutaj również zostały wykonane odpowiednie metody dla obiektów klas
pochodnych a nie metoda zgodna z obiektem jaki jest zadeklarowany jako parametr
funkcji czyli float Figura::pole(). Takie działanie jest spowodowane przez
przyjmowanie obiektu klasy Figura przez referencję. Gdyby obiekty były przyjmowane
przez wartość (parametr bez &) zostałaby wykonana 3 krotnie metoda float
Figura::pole() i 3 krotnie wyświetlona wartość -1.
Czysta wirtualność
Określa to, że metoda z klasy bazowej deklarująca metodę wirtualną nigdy nie
powinna się wykonać. W efekcie klasa taka staje się klasą abstrakcyjną. Oznacza to tyle, iż
nie jest możliwe stworzenie obiektu tej klasy. Klasa taka służy jedynie temu, by
zdefiniować pewnego rodzaju interfejs i jest przeznaczona jedynie po to, by od niej
dziedziczyć.
Metodę czysto wirtualną w języku C++ deklaruje się tak:
class Figura {
public:
virtual float pole() = 0;
};
Taka deklaracja metody wirtualnej zmusza jednocześnie do określenia metody float
pole() na jednym z poziomów dziedziczenia. Nie jest możliwe pominięcie takiej
implementacji. Jednocześnie taka deklaracja uniemożliwia stworzenie jakiegokolwiek
obiektu klasy Figura np.: Figura mojObiekt;.
Właściwości metod wirtualnych
" nie może być zadeklarowana jako statyczna (static).
" jeśli metoda wirtualna została zaimplementowana w jakimkolwiek wyższym
poziomie dziedziczenia (w szczególności w klasie bazowej całej struktury
dziedziczenia), nie jest konieczne podawanie implementacji w klasie pochodnej.
" jeśli w klasie jest zadeklarowana jakakolwiek metoda wirtualna, zaleca się aby
destruktor w tej klasie również określić jako wirtualny
Java
" W Javie domyślnie wszystkie metody są wirtualne. Aby jednak określić jakąś
metodę jako niewirtualną należy zadeklarować metodę jako final.
Zastosowania
" Rozszerzalność kodu. Polimorfizm umożliwia rozszerzanie nawet skompilowanych
fragmentów kodu.
" Pozwala na rozszerzalność kodu również wtedy, gdy dostępna jest jedynie
skompilowana wersja klasy bazowej.
" Zwalnia programistę od niepotrzebnego wysiłku.
" Programista nie musi przejmować się tym, którą z klas pochodnych aktualnie
obsługuje, a jedynie tym, jakie operacje chce na tej klasie wykonać.
" Programista myśli co ma wykonać a nie jak to coś wykonać - nie musi się
przejmować szczegółami implementacyjnymi.
4. Konstruktory i destruktory.
Zadania konstruktora
Wywołanie konstruktora powoduje wykonanie następujących zadań:
" obliczenie rozmiaru obiektu
" alokacja obiektu w pamięci
" wyczyszczenie (zerowanie) obszaru pamięci zarezerwowanej dla obiektu (tylko w
niektórych językach)
" wpisanie do obiektu informacji łączącej go z odpowiadającą mu klasą (połączenie z
metodami klasy)
" wykonanie kodu klasy bazowej (w niektórych językach nie wymagane)
" wykonanie kodu wywołanego konstruktora
Z wyjątkiem ostatniego punktu powyższe zadania są wykonywane wewnętrznie i są wszyte
w kompilator lub interpreter języka, lub w niektórych językach stanowią kod klasy
bazowej.
W językach programowania w różny sposób oznacza się konstruktor:
" w C++, Javie, C# - jest to metoda o nazwie zgodnej z nazwą klasy
" w Pascalu - metoda której nazwę poprzedzono słowem kluczowym constructor.
W języku C++ wyróżnia się następujące szczególne rodzaje konstruktorów:
 konstruktor domyślny,
 zwykły konstruktor,
 konstruktor kopiujący,
 konstruktor konwertujący,
Zwykły konstruktor
Konstruktor, który można wywołać, podając co najmniej jeden parametr. Jest to
zwykły konstruktor stworzony przez twórcę klasy. Jego zadeklarowanie w C++ nie
powoduje niejawnego generowania konstruktora domyślnego. Z reguły parametry takiego
zwykłego konstruktora spełniają funkcję inicjalizatorów, które przypisują odpowiednie
wartości wewnętrznym zmiennym tworzonego obiektu, np. (przykład w C++):
class Wektor {
public:
Wektor( double x , double y ) {
this->x = x;
this->y = y;
}
private:
double x;
double y;
};
int main () {
Wektor mojWektor( 3 , 2 );
return 0;
}
Właściwości i ciekawostki
" W większości języków konstruktor nie może być wirtualny(w efekcie czego nie może
być metodą czysto wirtualną).
" Konstruktor nie może być statyczny
" W klasie, gdzie zadeklarowany jest konstruktor kopiujący, powinien być
zadeklarowany dowolny inny konstruktor (domyślny lub inny), ponieważ nie byłoby
możliwe stworzenie obiektu danej klasy. Aby stworzyć obiekt korzystając z
konstruktora kopiującego, należałoby posiadać inny egzemplarz obiektu danej klasy,
który nie może być utworzony, ponieważ jego stworzenie również wymagałoby
egzemplarza danej klasy itd.
" W klasie, gdzie wymagane jest istnienie: konstruktora kopiującego lub destruktora
lub operatora przypisania, wymagane jest najczęściej istnienie wszystkich trzech.
" Parametr konstruktora kopiującego nie może być przekazywany przez wartość,
ponieważ powodowałoby to nieskończone wywołanie konstruktorów kopiujących.
Dla potrzeb wywołania konstruktora należałoby wykonać kopię obiektu. Aby
wykonać kopię obiektu należy wywołać jego konstruktor kopiujący, któremu również
należy przekazać obiekt przez wartość, a więc wykonać jego kopię, itd. Błąd ten nie
przejdzie procesu kompilacji, kompilator rozpoznaje taki przypadek i generuje
sygnał błędu. Nie jest możliwe wygenerowanie nieskończonej pętli wywołań,
ponieważ ciąg takich wywołań miałby teoretycznie nieskończoną długość i
spowodowałby zablokowanie kompilatora.
" Aby uniemożliwić stworzenie obiektu danej klasy należy:
" zadeklarować wszystkie konstruktory w sekcji prywatnej (konstruktor
kopiujący może ale nie musi spełniać tego warunku)
" klasa nie może deklarować przyjazni z klasą ani funkcją
Działanie takie stosuje się, gdy na przykład klasa ma służyć jako zbiór metod i pól
statycznych i nie jest potrzebny jakikolwiek egzemplarz obiektu danej klasy (również
jako klasy bazowej).
Destruktor - w obiektowych językach programowania specjalna metoda, wywoływana
przez program przed usunięciem obiektu i niemal nigdy nie jest wywoływana wprost w
kodzie używającym obiektu. Pod względem funkcjonalnym jest to przeciwieństwo
konstruktora.
Destruktor ma za zadanie wykonać czynności składające się na jego "zniszczenie",
inne niż zwolnienie pamięci zajmowanej przez sam obiekt, przygotowujące obiekt do
fizycznego usunięcia. Po jego wykonaniu obiekt znajduje się w stanie osobliwym i
zazwyczaj nie można już z tym obiektem zrobić nic poza fizycznym usunięciem. Destruktor
zwykle wykonuje takie czynności, jak zamknięcie połączenia z plikiem/gniazdem/potokiem,
odrejestrowanie się z innych obiektów, czasem również zanotowanie faktu usunięcia, a
także usunięcie obiektów podległych, które obiekt utworzył lub zostały mu przydzielone
jako podległe (jeśli jest ich jedynym właścicielem) lub wyrejestrowanie się z jego
użytkowania (jeśli jest to obiekt przezeń współdzielony).
W większości języków programowania (np C++, Object Pascal) destruktor jest
dziedziczony jak każda inna metoda.
Przykładowy destruktor (w składni C++):
class Samochod{
public:
string marka;
//... (pewne dane i metody)
~Samochod() { //destructor
std::cout << "Samochod " << marka << " zostal usuniety.\n";
}
};
Finalizator
W niektórych językach z wbudowanym odśmiecaczem (np. Java i C#) dostępna jest
składnia finalizatora - specjalnej metody wywoływanej, gdy obiekt jest usuwany przy
odśmiecaniu. W przeciwieństwie do destruktora nie wiadomo w którym dokładnie
momencie działania programu to nastąpi.


Wyszukiwarka