Projektowanie obiektowe Klasy abstrakcyjne Interfejsy

Transkrypt

Projektowanie obiektowe Klasy abstrakcyjne Interfejsy
Projektowanie
obiektowe
Roman Simiński
[email protected]
www.siminskionline.pl
Klasy abstrakcyjne
Interfejsy
Klasa abstrakcyjna ― po co?
W programowaniu obiektowym wykorzystywane są:
Abstrakcja – programowanie na na wysokim poziomie ogólności,
ukierunkowane na identyfikowanie głównych obiektów w systemie i ich
najważniejszych zachowań oraz na definiowanie głównych zależności pomiędzy
obiektami oraz ogólnych scenariuszy ich zachowań.
Rozszerzalność – programowanie pozwalające na definiowanie szczególnych
zachowań obiektów, realizowanych w ramach ogólnych scenariuszy, co zwykle
osiąga się z wykorzystaniem dziedziczenia i polimorfizmu.
Środkiem do osiągnięcia rozszerzalności jest:
Antycypacja – przewidywanie oraz odwzorowanie w kodzie przyszłych, choć
jeszcze nie do końca znanych zmian i modyfikacji wymagań dla systemu.
Projektowanie obiektowe | 2
Wskaźniki do obiektów
a hierarchia klas
Konwersja wskaźników
C++
Projektowanie obiektowe | 3
Upcasting, czyli dziecko staje się rodzicem
class
class Silnik
Silnik
{{
public:
public:
int
int pojemnosc;
pojemnosc;
int
int lbCylindrow;
lbCylindrow;
int
int moc;
moc;
};
};
Upcasting
Upcasting występuje
występuje wtedy,
wtedy, gdy
gdy do
do wskaźnika
wskaźnika lub
lub referencji
referencji klasy
klasy
bazowej,
bazowej, przypisujemy
przypisujemy odwołanie
odwołanie do
do obiektu
obiektu klasy
klasy pochodnej.
pochodnej.
ZZ obiektem
obiektem klasy
klasy pochodnej
pochodnej możemy
możemy robić
robić wszystko
wszystko to,
to, co
co
zz obiektem
obiektem klasy
klasy bazowej
bazowej (relacja
(relacja is-a).
is-a).
class
class SilnikTurbo
SilnikTurbo :: public
public Silnik
Silnik
{{
public:
public:
float
float cisnienieDoladowania;
cisnienieDoladowania;
};
};
.. .. ..
Silnik
Silnik ** s;
s;
ss == new
new SilnikTurbo;
SilnikTurbo;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
cout
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
<<
endl
endl
endl
endl
endl
endl
<<
<<
<<
<<
<<
<<
Upcasting
Upcasting nie
nie wymaga
wymaga żadnych
żadnych dodatkowych
dodatkowych operacji,
operacji,
jest
jest legalny
legalny ii bezpieczny.
bezpieczny.
s->pojemnosc;
s->pojemnosc;
s->moc;
s->moc;
s->lbCylindrow;
s->lbCylindrow;
Projektowanie obiektowe | 4
Upcasting działa ze wskaźnikami i referencjami
Silnik
Silnik ** s;
s;
ss == new
new SilnikTurbo;
SilnikTurbo;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
Upcasting
Upcasting via
via wskaźnik
wskaźnik ii referencja,
referencja, obiekt
obiekt
anonimowy
anonimowy tworzony
tworzony dynamicznie
dynamicznie
Silnik
Silnik && sr
sr == *s;
*s;
sr.pojemnosc
sr.pojemnosc == 2400;
2400;
sr.moc
sr.moc == 163;
163;
sr.lbCylindrow
sr.lbCylindrow == 5;
5;
SilnikTurbo
SilnikTurbo silT;
silT;
Silnik
Silnik ** ss == &silT;
&silT;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
Upcasting
Upcasting via
via wskaźnik
wskaźnik ii referencja
referencja do
do
zwykłego
zwykłego obiektu
obiektu
Silnik
Silnik && sr
sr == silT;
silT;
sr.pojemnosc
sr.pojemnosc == 3200;
3200;
sr.moc
sr.moc == 240;
240;
sr.lbCylindrow
sr.lbCylindrow == 8;
8;
Projektowanie obiektowe | 5
Upcasting oznacza chwilowe ograniczenie dostępności pól „pochodnych”
class
class Silnik
Silnik
{{
public:
public:
int
int pojemnosc;
pojemnosc;
int
int lbCylindrow;
lbCylindrow;
int
int moc;
moc;
};
};
Utworzony
Utworzony obiekt
obiekt jest
jest reprezentantem
reprezentantem klasy
klasy pochodnej
pochodnej
SilnikTurbo,
SilnikTurbo, jednak
jednak lokalizujący
lokalizujący go
go wskaźnik
wskaźnik związany
związany jest
jest zz klasą
klasą
bazową
bazową Silnik.
Silnik. Doszło
Doszło do
do upcastingu
upcastingu —
— dla
dla kompilatora
kompilatora wskazywany
wskazywany
obiekt
obiekt jest
jest teraz
teraz obiektem
obiektem klasy
klasy Silnik.
Silnik.
class
class SilnikTurbo
SilnikTurbo :: public
public Silnik
Silnik
{{
public:
public:
float
float cisnienieDoladowania;
cisnienieDoladowania;
};
};
.. .. ..
Silnik
Silnik ** s;
s;
ss == new
new SilnikTurbo;
SilnikTurbo;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.9;
0.9;
cout
cout
cout
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
<<
<<
<<
endl
endl
endl
endl
endl
endl
endl
endl
<<
<<
<<
<<
<<
<<
<<
<<
Ponieważ
Ponieważ wskaźnik
wskaźnik jest
jest związany
związany zz klasą
klasą Silnik,
Silnik, aa w
w niej
niej
nie
nie ma
ma pola
pola cisnienieDoladowania,
cisnienieDoladowania, odwołania
odwołania to
to są
są
nieprawidłowe
nieprawidłowe —
— mimo
mimo że
że tak
tak naprawdę
naprawdę wskazywany
wskazywany
obiekt
obiekt jest
jest klasy
klasy SilnikTurbo.
SilnikTurbo.
s->pojemnosc;
s->pojemnosc;
s->moc;
s->moc;
s->lbCylindrow;
s->lbCylindrow;
s->cisnienieDoladowania;
s->cisnienieDoladowania;
Projektowanie obiektowe | 6
Upcasting czasem oznacza ograniczenie dostępności pól
Dostęp do obiektu poprzez wskaźnik jego klasy
SilnikTurbo
SilnikTurbo
SilnikTurbo ** s;
s;
ss == new
new SilnikTurbo;
SilnikTurbo;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.9;
0.9;
Silnik
Pełny
Pełny dostęp
dostęp do
do
własnych
własnych pól
pól oraz
oraz
pojemnosc
lbCylindrow
moc
pól
pól odziedziczonych.
odziedziczonych.
...
cisnienieDoladowania
...
Dostęp do obiektu poprzez wskaźnik jego klasy bazowej — upcasting
SilnikTurbo
Silnik
Silnik ** s;
s;
ss == new
new SilnikTurbo;
SilnikTurbo;
s->pojemnosc
s->pojemnosc == 2400;
2400;
s->moc
s->moc == 163;
163;
s->lbCylindrow
s->lbCylindrow == 5;
5;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.9;
0.9;
Silnik
Dostępne
Dostępne tylko
tylko
pola
pola odziedziczone,
odziedziczone, czyli
czyli
jak
jak w
w klasie
klasie bazowej.
bazowej.
pojemnosc
lbCylindrow
moc
...
cisnienieDoladowania
...
Projektowanie obiektowe | 7
Dlaczego upcasting jest potrzebny? Bo jest wygodny! I daje możliwości!
void
void zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( Silnik
Silnik
{{
s->pojemnosc
s->pojemnosc == 0;
0;
s->moc
s->moc == 0;
0;
s->lbCylindrow
s->lbCylindrow == 0;
0;
}}
void
void pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika( Silnik
Silnik
{{
cout
cout <<
<< endl
endl <<
<< s->pojemnosc;
s->pojemnosc;
cout
cout <<
<< endl
endl <<
<< s->moc;
s->moc;
cout
cout <<
<< endl
endl <<
<< s->lbCylindrow;
s->lbCylindrow;
}}
.. .. ..
Silnik
Silnik sil;
sil;
SilnikTurbo
SilnikTurbo silT;
silT;
SilnikHybryda
SilnikHybryda silH;
silH;
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( &sil
&sil );
);
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( &silT
&silT );
);
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( &silH
&silH );
);
silT.cisnienieDoladowania
silT.cisnienieDoladowania == 0;
0;
silH.mocElektryczna
silH.mocElektryczna == 0;
0;
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
&sil
&sil );
);
&silT
&silT );
);
&silH
&silH );
);
** ss ))
** ss ))
class
class Silnik
Silnik
{{
public:
public:
int
int pojemnosc;
pojemnosc;
int
int lbCylindrow;
lbCylindrow;
int
int moc;
moc;
};
};
class
class SilnikTurbo
SilnikTurbo
:: public
public Silnik
Silnik
{{
public:
public:
float
float cisnienieDoladowania;
cisnienieDoladowania;
};
};
class
class SilnikHybryda
SilnikHybryda
:: public
public Silnik
Silnik
{{
public:
public:
int
int mocElektryczna;
mocElektryczna;
};
};
Projektowanie obiektowe | 8
Upcasting działa również z referencjami
void
void zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( Silnik
Silnik
{{
s.pojemnosc
s.pojemnosc == 0;
0;
s.moc
s.moc == 0;
0;
s.lbCylindrow
s.lbCylindrow == 0;
0;
}}
void
void pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika( Silnik
Silnik
{{
cout
cout <<
<< endl
endl <<
<< s.pojemnosc;
s.pojemnosc;
cout
cout <<
<< endl
endl <<
<< s.moc;
s.moc;
cout
cout <<
<< endl
endl <<
<< s.lbCylindrow;
s.lbCylindrow;
}}
.. .. ..
Silnik
Silnik sil;
sil;
SilnikTurbo
SilnikTurbo silT;
silT;
SilnikHybryda
SilnikHybryda silH;
silH;
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( sil
sil );
);
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( silT
silT );
);
zerujBazoweDaneSilnika(
zerujBazoweDaneSilnika( silH
silH );
);
silT.cisnienieDoladowania
silT.cisnienieDoladowania == 0;
0;
silH.mocElektryczna
silH.mocElektryczna == 0;
0;
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
pokazBazoweDaneSilnika(
sil
sil );
);
silT
silT );
);
silH
silH );
);
&& ss ))
&& ss ))
class
class Silnik
Silnik
{{
public:
public:
int
int pojemnosc;
pojemnosc;
int
int lbCylindrow;
lbCylindrow;
int
int moc;
moc;
};
};
class
class SilnikTurbo
SilnikTurbo
:: public
public Silnik
Silnik
{{
public:
public:
float
float cisnienieDoladowania;
cisnienieDoladowania;
};
};
class
class SilnikHybryda
SilnikHybryda
:: public
public Silnik
Silnik
{{
public:
public:
int
int mocElektryczna;
mocElektryczna;
};
};
Projektowanie obiektowe | 9
Czy jest downcasting? Tak, ale on może być przyczyną klopotów...
SilnikTurbo
SilnikTurbo silT;
silT;
s.pojemnosc
s.pojemnosc == 2400;
2400;
s.moc
s.moc == 163;
163;
s.lbCylindrow
s.lbCylindrow == 5;
5;
s.cisnienieDoladowania
s.cisnienieDoladowania == 0.9;
0.9;
silT : SilnikTurbo
pojemnosc
lbCylindrow
moc
cisnienieDoladowania
2400
5
163
0.9
Upcasting:
Silnik
Silnik ** s;
s;
ss == &silT;
&silT;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.5;
0.5;
.. .. ..
s
pojemnosc
lbCylindrow
moc
2400
pojemnosc
lbCylindrow
moc
2400
5
163
Downcasting:
.. .. ..
SilnikTurbo
SilnikTurbo ** st;
st;
st
st == (( SilnikTurbo
SilnikTurbo ** )s;
)s;
st->cisnienieDoladowania
st->cisnienieDoladowania == 0.5;
0.5;
st
cisnienieDoladowania
5
163
0.9
Downcasting:
Downcasting: konwersja
konwersja wskazania
wskazania na
na obiekt
obiekt klasy
klasy bazowej
bazowej do
do wskaźnika
wskaźnika na
na klasę
klasę pochodną.
pochodną.
Możliwe
Możliwe tylko
tylko poprzez
poprzez konwersję
konwersję typów
typów wskaźników
wskaźników jawnie
jawnie zapisaną
zapisaną przez
przez programistę.
programistę.
Projektowanie obiektowe | 10
Wskaźniki pozwalają „patrzeć” na obiekt w różny sposób
SilnikTurbo
SilnikTurbo silT;
silT;
s.pojemnosc
s.pojemnosc == 2400;
2400;
s.moc
s.moc == 163;
163;
s.lbCylindrow
s.lbCylindrow == 5;
5;
s.cisnienieDoladowania
s.cisnienieDoladowania == 0.9;
0.9;
silT : SilnikTurbo
pojemnosc
lbCylindrow
moc
cisnienieDoladowania
Upcasting:
Silnik
Silnik ** s;
s;
ss == &silT;
&silT;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.5;
0.5;
.. .. ..
5
163
0.9
Wskaźnik s „widzi” obiekt silT tak:
s
Downcasting:
.. .. ..
SilnikTurbo
SilnikTurbo ** st;
st;
st
st == (( SilnikTurbo
SilnikTurbo ** )s;
)s;
st->cisnienieDoladowania
st->cisnienieDoladowania == 0.5;
0.5;
2400
pojemnosc
lbCylindrow
moc
2400
5
163
Wskaźnik st „widzi” obiekt silT tak:
st
pojemnosc
lbCylindrow
moc
cisnienieDoladowania
2400
5
163
0.9
W
W tym
tym przypadku
przypadku downcasting
downcasting jest
jest bezpieczny
bezpieczny —
— obiekt
obiekt wskazywany
wskazywany przez
przez ss jest
jest rzeczywiście
rzeczywiście
obiektem
obiektem klasy
klasy SilnikTurbo
SilnikTurbo ii downcasting
downcasting „odsłania”
„odsłania” przesłonięte
przesłonięte pole
pole cisnienieDoladowania.
cisnienieDoladowania.
Projektowanie obiektowe | 11
Ale teraz będą kłopoty...
Silnik
Silnik sil;
sil;
s.pojemnosc
s.pojemnosc == 2400;
2400;
s.moc
s.moc == 163;
163;
s.lbCylindrow
s.lbCylindrow == 5;
5;
sil : Silnik
pojemnosc
lbCylindrow
moc
Zakotwiczenie wskaźnika:
Silnik
Silnik ** s;
s;
ss == &sil;
&sil;
s->cisnienieDoladowania
s->cisnienieDoladowania == 0.5;
0.5;
.. .. ..
5
163
Wskaźnik s „widzi” obiekt silT tak:
s
pojemnosc
lbCylindrow
moc
Downcasting:
.. .. ..
SilnikTurbo
SilnikTurbo ** st;
st;
st
st == (( SilnikTurbo
SilnikTurbo ** )s;
)s;
st->cisnienieDoladowania
st->cisnienieDoladowania == 0.5;
0.5;
2400
2400
5
163
Wskaźnik st „widzi” obiekt silT tak:
st
pojemnosc
lbCylindrow
moc
Tego nie ma!
cisnienieDoladowania
2400
5
163
0.9
W
W tym
tym przypadku
przypadku downcasting
downcasting jest
jest błędny
błędny —
— obiekt
obiekt wskazywany
wskazywany przez
przez ss nie
nie jest
jest
obiektem
obiektem klasy
klasy SilnikTurbo
SilnikTurbo ii downcasting
downcasting produkuje
produkuje nieistniejące
nieistniejące pole
pole cisnienieDoladowania.
cisnienieDoladowania.
Projektowanie obiektowe | 12
Downcasting wykonywany poprzez dynamic_cast
Nie do końca bezpieczne metody konwersji wskaźników:
Silnik
Silnik ** s;
s;
SilnikTurbo
SilnikTurbo ** st;
st;
.. .. ..
st
st == (( SilnikTurbo
SilnikTurbo ** )s;
)s;
st
st == static_cast<
static_cast< SilnikTurbo
SilnikTurbo ** >(
>( ss );
);
st
st == reinterpret_cast<
reinterpret_cast< SilnikTurbo
SilnikTurbo ** >(
>( ss );
);
Rzutowanie z kontrolą jego poprawności:
st
st == dynamic_cast<
dynamic_cast< SilnikTurbo
SilnikTurbo ** >(
>( ss );
);
if(
if( st
st !=
!= 00 ))
st->cisnienieDoladowania
st->cisnienieDoladowania == 0.5;
0.5;
Operator
Operator dynamic_cast
dynamic_cast pozwala
pozwala na
na konwersję
konwersję wskaźników
wskaźników zz kontrolą,
kontrolą, czy
czy taka
taka
konwersja
konwersja może
może być
być przeprowadzona.
przeprowadzona. Jeżeli
Jeżeli możliwa
możliwa jest
jest konwersja
konwersja do
do postaci
postaci
wskaźnika
wskaźnika na
na klasę
klasę pochodną,
pochodną, operator
operator taką
taką konwersję
konwersję realizuje.
realizuje. Jeżeli
Jeżeli konwersja
konwersja
nie
nie może
może zostać
zostać zrealizowana,
zrealizowana, rezultatem
rezultatem operatora
operatora jest
jest wartość
wartość zerowa.
zerowa.
Mechanizm
Mechanizm pozwalający
pozwalający na
na dynamic_cast,
dynamic_cast, znany
znany jest
jest jako
jako Run-Time
Run-Time Type
Type
Information
Information —
— RTTI.
RTTI.
Projektowanie obiektowe | 13
Polimorfizm
w akcji
Projektowanie obiektowe | 14
Polimorfizm w akcji
Należy napisać program wspomagający pracę technologa w firmie produkującej okna.
Zadaniem programu jest:
obliczanie łącznego pola powierzchni wszystkich skrzydeł okna,
przybliżonej, łącznej długości profili, wymaganych do produkcji każdego ze skrzydeł.
Projektowanie obiektowe | 15
Analiza obiektowa
Stosując zasadę abstrakcji wyodrębniamy najistotniejsze cechy obiektów dla
rozpatrywanego zagadnienia — szyby to figury geometryczne a okno to ich złożenie.
Łączna powierzchnia okna to, w przybliżeniu, suma pól figur opisujących szyby,
Łączna długość profili to, w przybliżeniu, suma obwodów figur opisujących szyby.
Kwadrat
Trójkąt
Prostokąt
Prostokąt
Obiekty rzeczywiste
Abstrakcyjny
model analityczny
Projektowanie obiektowe | 16
Analiza obiektowa, cd...
Realizacja programu sprowadza się do obliczeń pól powierzchni i obwodów obiektów,
będących złożeniem elementarnych figur płaskich.
Utworzyliśmy już klasy reprezentujące figury płaskie,
Nie wiemy jak reprezentować ich złożenia.
Kwadrat
Trójkąt
Prostokąt
Prostokąt
Kwadrat
Prostokąt
Prostokąt
Trójkąt
Projektowanie obiektowe | 17
Figury płaskie raz jeszcze — budujemy hierarchię klas
Rozpoczynamy od utworzenia nieco dziwnej klasy — reprezentującej jakąś
abstrakcyjną figurę geometryczną. Wyposażamy ją w funkcje obliczania pola
i obwodu.
class
class Figure
Figure
{{
public
public ::
Figure()
Figure() {}
{}
double
double area()
area() const
const {{ return
return
double
double circumference()
circumference() const
const
const
const char
char ** getName()
getName() const
const
};
};
0;
0; }}
{{ return
return
{{ return
return
0;
0; }}
"Figura";
"Figura"; }}
Klasa Figure służyć będzie jak klasa bazowa dla specjalizowanych klas pochodnych,
reprezentujących konkretne figury geometryczne.
Jej istotą jest stwierdzenie, że każda figura geometryczna powinna umieć wyznaczać
swoje pole i obwód, w charakterystyczny dla siebie sposób.
Zatem każda z klas pochodnych, powinna redefiniować funkcje area i circumference, w
odpowiedni dla tych klas sposób.
Dodatkowo klasa Figure i jej pochodne posiadać będę funkcję getName(), jej
rezultatem będzie nazwa klasy.
Projektowanie obiektowe | 18
Klasa Square jako pochodna klasy Figure
Klasa reprezentująca kwadrat będzie teraz klasą pochodną klasy Figure:
class
class Square:
Square: public
public Figure
Figure
{{
public
public ::
Square(
Square( double
double startSide
startSide == 00 );
);
void
setSide(
void
setSide( double
double newSide
newSide );
);
double
double getSide();
getSide();
double
double area()
area() const;
const;
double
double circumference()
circumference()
const
const char
char ** getName()
getName()
const;
const;
const;
const;
private:
private:
double
double side;
side;
};
};
Redefinicja funkcji area i circumference i getName dla kwadratu:
double
double Square::area()
Square::area() const
const {{ return
return
double
double Square::circumference()
Square::circumference() const
const
const
const char
char ** Square::getName()
Square::getName() const
const
side
side ** side;
side; }}
{{ return
return 44 ** side;
side; }}
{{ return
return "Kwadrat";
"Kwadrat"; }}
Projektowanie obiektowe | 19
Klasa Rectangle jako pochodna klasy Figure
Klasa reprezentująca prostokąt będzie teraz klasą pochodną klasy Figure:
class
class Rectangle:
Rectangle: public
public Figure
Figure
{{
public
public ::
Rectangle(
Rectangle( double
double startWidth
startWidth == 0,
0, double
double startHeight
startHeight == 00 );
);
void
void setWidth(
setWidth( double
double newWidth
newWidth );
);
void
void setHeight(
setHeight( double
double newHeight
newHeight );
);
double
getWidth()
const;
double getWidth() const;
double
double getHeight()
getHeight() const;
const;
double
double area()
area() const;
const;
double
double circumference()
circumference()
const
const char
char ** getName()
getName()
const;
const;
const;
const;
private:
private:
double
double width,
width, height;
height;
};
};
Redefinicja funkcji area i circumference i getName dla prostokąta:
double
double Rectangle::area()
Rectangle::area() const
const {{ return
return
double
double Rectangle::circumference()
Rectangle::circumference() const
const
const
char
*
Rectangle::getName()
const
const char * Rectangle::getName() const
width
width ** height;
height; }}
{{ return
return 22 ** width
width ++ 22 ** height;
height; }}
{{ return
return "Prostokat";
"Prostokat"; }}
Projektowanie obiektowe | 20
Klasa Triangle jako pochodna klasy Figure
Klasa reprezentująca trójkąt będzie teraz klasą pochodną klasy Figure:
class
class Triangle:
Triangle: public
public Figure
Figure
{{
public
public ::
Triangle(
Triangle( double
double startBase
startBase == 0,
0, double
double startHeight
startHeight == 00 );
);
void
void setBase(
setBase( double
double newBase
newBase );
);
void
void setHeight(
setHeight( double
double newHeight
newHeight );
);
double
getBase()
const;
double getBase() const;
double
double getHeight()
getHeight() const;
const;
double
double area()
area() const;
const;
double
double circumference()
circumference()
const
const char
char ** getName()
getName()
private:
private:
double
double base,
base, height;
height;
};
};
const;
const;
const;
const;
Uwaga,
Uwaga, zakładamy
zakładamy dla
dla uproszczenia,
uproszczenia, że
że trójkątne
trójkątne
szyby
szyby będą
będą trójkątami
trójkątami prostokątnymi.
prostokątnymi.
Redefinicja funkcji area i circumference i getName dla trójkąta:
double
double Triangle::area()
Triangle::area() const
const {{ return
return 0.5
0.5 ** base
base ** height;
height; }}
double
double Triangle::circumference()
Triangle::circumference() const
const {{
return
return sqrt(
sqrt( base
base ** base
base ++ height
height ** height
height )) ++ base
base ++ height;
height;
}}
const
const char
char ** Triangle::getName()
Triangle::getName() const
const {{ return
return "Trojkat";
"Trojkat"; }}
Projektowanie obiektowe | 21
Hierarchia klas figur płaskich
Figure
+double area()
+double circumference()
+const char * getName()
Rectangle
Square
Triangle
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
―double height
―double width
―double side
―double base
―double width
Projektowanie obiektowe | 22
Krok w stronę polimorfizmu — upcasting z wykorzystaniem wskaźnika
Załóżmy, że zdefiniowano wskaźnik do klasy bazowej Figure:
Figure
Figure ** fp;
fp;
Załóżmy, że zdefiniowano obiekty klas pochodnych:
Square
Square
Rectangle
Rectangle
Triangle
Triangle
s(
s(
r(
r(
t(
t(
10
10 );
);
10,
10, 20
20 );
);
10,10
10,10 );
);
Wiemy już, że można przypisać do wskaźnika fp wskazanie na obiekt klasy pochodnej:
fp
fp
fp
fp
fp
fp
==
==
==
&s;
&s;
&r;
&r;
&t;
&t;
//
//
//
//
//
//
OK
OK
OK
OK
OK
OK
Projektowanie obiektowe | 23
Polimorfizm w akcji...?
Figure
Figure ** fp;
fp;
Square
Square
Rectangle
Rectangle
Triangle
Triangle
s(
s(
r(
r(
t(
t(
10
10 );
);
10,
10, 20
20 );
);
10,10
10,10 );
);
fp
fp ==
cout
cout
cout
cout
cout
cout
&s;
&s;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
fp
fp ==
cout
cout
cout
cout
cout
cout
&r;
&r;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
fp
fp ==
cout
cout
cout
cout
cout
cout
&t;
&t;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
Ale to nie działa! Obiekty zachowują się tak, jakby były obiektami klasy Figure!
Zapomnieliśmy o funkcjach wirtualnych...!
Projektowanie obiektowe | 24
Zapomnieliśmy o funkcjach wirtualnych!
Jest:
class
class Figure
Figure
{{
public
public ::
Figure()
Figure() {}
{}
double
double area()
area() const
const {{ return
return
double
double circumference()
circumference() const
const
const
const char
char ** getName()
getName() const
const
};
};
0;
0; }}
{{ return
return
{{ return
return
0;
0; }}
"Figura";
"Figura"; }}
Powinno być:
class
class Figure
Figure
{{
public
public ::
Figure()
Figure() {}
{}
virtual
virtual double
double area()
area() const
const {{ return
return
virtual
virtual double
double circumference()
circumference() const
const
virtual
virtual const
const char
char ** getName()
getName() const
const
};
};
0;
0; }}
{{ return
return 0;
0; }}
{{ return
return "Figura";
"Figura"; }}
Nie ma polimorfizmu bez metod wirtualnych w klasach tworzących hierarchię
dziedziczenia.
Projektowanie obiektowe | 25
Wiązanie statyczne i funkcje niewirtualne — raz jeszcze
O tym która funkcja jest wywoływana decyduje kompilator na etapie kompilacji.
Wywołanie ma taką samą postać jak wywołanie klasycznych funkcji w języku C.
O tym która funkcja zostanie wywołana decyduje typ występujący w deklaracji
zmiennej wskaźnikowej.
Square
Figure
Figure ** fp;
fp;
Square
Square s(
s( 10
10 );
);
fp
fp ==
cout
cout
cout
cout
&s;
&s;
<<
<< fp->area()
fp->area() <<
<< endl;
endl;
<<
<< fp->circumference()
fp->circumference() <<
<< endl;
endl;
Mimo
Mimo iż
iż klasa
klasa Square
Square przedefiniowała
przedefiniowała obie
obie funkcje
funkcje
i posiada
i posiada ich
ich własne
własne wersje,
wersje, nie
nie zostaną
zostaną one
one
Figure
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
wywołane.
wywołane.
―double side
Projektowanie obiektowe | 26
Wiązanie dynamiczne i funkcje wirtualne — raz jeszcze
O tym która funkcja jest wywoływana decyduje typ obiektu wskazywanego.
Funkcja wiązana dynamicznie musi być zadeklarowana jako wirtualna.
Wystarczy, że słowo kluczowe virtual wystąpi w deklaracji klasy bazowej.
Square
Figure
Te
Te funkcje
funkcje zostały
zostały „nadpisane”
„nadpisane” w
w klasie
klasie pochodnej.
pochodnej.
Figure
Figure ** fp;
fp;
Square
Square s(
s( 10
10 );
);
fp
fp ==
cout
cout
cout
cout
&s;
&s;
<<
<< fp->area()
fp->area() <<
<< endl;
endl;
<<
<< fp->circumference()
fp->circumference() <<
<< endl;
endl;
Klasa
Klasa Square
Square przedefiniowała
przedefiniowała obie
obie funkcje
funkcje wirtualne.
wirtualne.
To
To one
one właśnie
właśnie zostaną
zostaną wywołane
wywołane aa nie
nie funkcje
funkcje zz klasy
klasy
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
―double side
bazowej.
bazowej.
Projektowanie obiektowe | 27
Jeszcze raz z funkcjami wirtualnymi...
Figure
Figure ** fp;
fp;
Square
Square
Rectangle
Rectangle
Triangle
Triangle
s(
s(
r(
r(
t(
t(
10
10 );
);
10,
10, 20
20 );
);
10,10
10,10 );
);
fp
fp ==
cout
cout
cout
cout
cout
cout
&s;
&s;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
fp
fp ==
cout
cout
cout
cout
cout
cout
&r;
&r;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
fp
fp ==
cout
cout
cout
cout
cout
cout
&t;
&t;
<<
<< endl
endl
<<
<< endl
endl
<<
<< endl
endl
<<
<<
<<
<<
<<
<<
"Figura:
"Figura:
"" Pole:
Pole:
"" Obwod:
Obwod:
""
""
""
<<
<<
<<
<<
<<
<<
fp->getName();
fp->getName();
fp->area();
fp->area();
fp->circumference();
fp->circumference();
Polimorfizm = dziedziczenie, redefinicja metod wirtualnych, wskaźniki
(referencje) i upcasting.
Projektowanie obiektowe | 28
Jak to wszystko wykorzystać w programie „okiennym”?
Zadany jest układ okna oraz wymiary jego ościeżnic. Jedna ościeżnica jest kwadratowa
(bok 50cm), pozostałe trzy prostokątne (70x50 cm, 50x100 cm i 70x100 cm).
Należy wyznaczyć łączną powierzchnię szyb i długość profili.
70
100
50
50
Projektowanie obiektowe | 29
Jak reprezentować informacje o oknie?
50
70
const
const int
int itemNo
itemNo == 4;
4;
50
Figure
Figure ** win[
win[ itemNo
itemNo ];
];
0
1
2
3
100
win
Kwadrat
Prostokąt
70
Prostokąt
50
70
100
100
50
50
50
Prostokąt
Projektowanie obiektowe | 30
Jak to wygląda w pamięci operacyjnej?
0
1
2
3
win
Square
Rectangle
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
―double side:
―double height:70
―double width: 100
50
Rectangle
+double area()
+double circumference()
+const char * getName()
―double height:50
―double width: 70
Rectangle
+double area()
+double circumference()
+const char * getName()
―double height:50
―double width: 100
Projektowanie obiektowe | 31
Definiowanie elementów okna, obliczenia, sprzątanie
//
// Okre
Okreśślenie
lenie liczby
liczby elementów
elementów
const
const int
int itemNo
itemNo == 4;
4;
//
// Tablica
Tablica wka
wkaźźników
ników na
na elementy
elementy okna
okna
Figure
Figure ** win[
win[ itemNo
itemNo ];
];
//
// Przydział
Przydział pami
pamięęci
ci dla
dla elementów
elementów okna
okna
win[
win[ 00 ]] == new
new Square(
Square( 50
50 );
);
win[
win[ 11 ]] == new
new Rectangle(
Rectangle( 50,
50, 70
70 );
);
win[
win[ 22 ]] == new
new Rectangle(
Rectangle( 50,
50, 100
100 );
);
win[
win[ 33 ]] == new
new Rectangle(
Rectangle( 70,
70, 100
100 );
);
//
// Oblicz
Oblicz co
co trzeba
trzeba
calcAndShowWinInfo(
calcAndShowWinInfo(
ii wyprowadz
wyprowadz do
do strumienia
strumienia wyj
wyjśściowego
ciowego
win,
win, 44 );
);
//
// Zwolnij
Zwolnij pami
pamięęćć przydzielon
przydzielonąą elementom
elementom okna
okna
for(
for( int
int ii == 0;
0; ii << itemNo;
itemNo; delete
delete win[
win[ i++]
i++] ))
;;
Projektowanie obiektowe | 32
Funkcja calcAndShowWinInfo
void
void calcAndShowWinInfo(
calcAndShowWinInfo( Figure
Figure ** window[],
window[], int
int numOfSash
numOfSash ))
{{
//
// Tutaj
Tutaj łłąączna
czna powierzchnia
powierzchnia szyb
szyb ii długo
długośśćć profili
profili
double
double totalArea
totalArea == 0,
0, totalCircum
totalCircum == 0;
0;
//
// Zliczanie
Zliczanie powierzchni
powierzchni ii długo
długośści
ci
for(
for( int
int ii == 0;
0; ii << numOfSash;
numOfSash; i++
i++ ))
{{
totalArea
+=
totalArea
+= window[
window[ ii ]->area();
]->area();
totalCircum
totalCircum +=
+= window[
window[ ii ]->circumference();
]->circumference();
}}
}}
//
// Wyprowadzanie
Wyprowadzanie wyników
wyników obliczeń
obliczeń
cout
cout <<
<< "Powierzchnia
"Powierzchnia szyb
szyb :: "" <<
<<
cout
:: "" <<
cout <<
<< "Dlugosc
"Dlugosc profili
profili
<<
cout
cout <<
<< endl;
endl;
totalArea
totalArea <<
<< endl;
endl;
totalCircum
totalCircum <<
<< endl;
endl;
Projektowanie obiektowe | 33
Iteracja krok po kroku
for(
for( int
int ii == 0;
0; ii << numOfSash;
numOfSash; i++
i++ ))
{{
totalArea
+=
totalArea
+= window[
window[ ii ]->area();
]->area();
totalCircum
totalCircum +=
+= window[
window[ ii ]->circumference();
]->circumference();
}}
0
1
2
i
0
3
win
Rectangle
Square
+double area()
+double circumference()
+double area()
+double circumference()
―double side:
50
double
double Square::area()
Square::area() const
const
―double height:70
{{
―double width: 100
return
return side
side ** side;
side;
}}
Rectangle
Rectangle
double
double Square::circumference()
Square::circumference() const
const
{{
+double
area()
+double area()
return
44 ** side;
+double
circumference()
return
side; +double circumference()
}}
―double height:50
―double height:50
―double width: 70
―double width: 100
Projektowanie obiektowe | 34
Iteracja krok po kroku
for(
for( int
int ii == 0;
0; ii << numOfSash;
numOfSash; i++
i++ ))
{{
totalArea
+=
totalArea
+= window[
window[ ii ]->area();
]->area();
totalCircum
totalCircum +=
+= window[
window[ ii ]->circumference();
]->circumference();
}}
0
1
2
i
1
3
win
Rectangle
Square
+double area()
+double area()
+double circumference()
―double side:
50
Rectangle
double
+doubleconst
circumference()
double Rectangle::area()
Rectangle::area()
const
{{
―double height:70
return
return width
width ** height;
height;
―double width: 100
}}
double
double Rectangle::circumference()
Rectangle::circumference()
const
Rectangle
const {{
return
return 22 ** width
width ++ 22 ** height;
height;
+double
area()
}}
+double area()
+double circumference()
+double circumference()
―double height:50
―double width: 70
―double height:50
―double width: 100
Projektowanie obiektowe | 35
Iteracja krok po kroku
for(
for( int
int ii == 0;
0; ii << numOfSash;
numOfSash; i++
i++ ))
{{
totalArea
+=
totalArea
+= window[
window[ ii ]->area();
]->area();
totalCircum
totalCircum +=
+= window[
window[ ii ]->circumference();
]->circumference();
}}
0
1
2
i
2
3
win
Rectangle
Square
+double area()
double
Rectangle::area()
+double
circumference()
double
Rectangle::area() const
const
+double area()
+double circumference()
return
―double
side: width
50
return
width ** height;
height;
―double height:70
―double width: 100
{{
}}
double
double Rectangle::circumference()
Rectangle::circumference()
Rectangle
const
{
const {
return
return 22 ** width
width ++ 22 ** height;
height;
+double area()
}}
+double circumference()
―double height:50
―double width: 70
Rectangle
+double area()
+double circumference()
―double height:50
―double width: 100
Projektowanie obiektowe | 36
Iteracja krok po kroku
for(
for( int
int ii == 0;
0; ii << numOfSash;
numOfSash; i++
i++ ))
{{
totalArea
+=
totalArea
+= window[
window[ ii ]->area();
]->area();
totalCircum
totalCircum +=
+= window[
window[ ii ]->circumference();
]->circumference();
}}
0
1
2
i
3
3
win
Rectangle
Square
+double area()
+double circumference()
+double area()
+double circumference()
―double side:
―double height:70
―double width: 100
50
double
double Rectangle::area()
Rectangle::area() const
const
{{
return
width
Rectangle
Rectangle
return
width ** height;
height;
}}
+double area()
+double area()
double
Rectangle::circumference()
double
Rectangle::circumference()
+double circumference()
+double circumference()
const
const {{
―double
height:50
height:50
return
22 ** width
** height;
return
width ++ 22―double
height;
―double
width: 70
―double width: 100
}}
Projektowanie obiektowe | 37
Klasy abstrakcyjne
C++
Projektowanie obiektowe | 38
Figury płaskie raz jeszcze — klasa abstrakcyjna
Klasa bazowa, określająca protokół rozmowy z obiektami klas pochodnych, powinna
być klasą abstrakcyjną:
class
class Figure
Figure
{{
public
public ::
Figure()
Figure() {}
{}
virtual
virtual
virtual
virtual
};
};
double
double
double
double
Funkcje
Funkcje abstrakcyjne
abstrakcyjne (ang.
(ang. pure
pure virtual
virtual functions).
functions).
Muszą
Muszą zostać
zostać zdefiniowane
zdefiniowane w
w każdej
każdej nieabstrakcyjnej
nieabstrakcyjnej
klasie
klasie pochodnej.
pochodnej.
area()
area() const
const == 0;
0;
circumference()
circumference() const
const == 0;
0;
const
const char
char ** getName()
getName() const
const {{ return
return "Figura";
"Figura"; }}
Służy ona jak wzorcowa klasa bazowa dla specjalizowanych klas pochodnych,
reprezentujących konkretne figury geometryczne.
Każda pochodna klasa nieabstrakcyjna musi zdefiniować własną wersją czystej funkcji
wirtualnej.
Projektowanie obiektowe | 39
Figury płaskie raz jeszcze — klasa abstrakcyjna, cd. ...
class
class Figure
Figure
{{
public
public ::
Figure()
Figure() {}
{}
virtual
virtual double
double area()
area() const
const == 0;
0;
virtual
virtual double
double circumference()
circumference() const
const == 0;
0;
const
const char
char ** getName()
getName() const
const {{ return
return "Figura";
"Figura"; }}
};
};
Nie można zdefiniować obiektów abstrakcyjnej klasy Figure.
Figure
Figure f;
f; //
// Nie
Nie definiujemy
definiujemy obiektów
obiektów klas
klas abstrakcyjnych
abstrakcyjnych
Wolno jednak posługiwać się wskaźnikami i referencjami do klasy Figure.
Figure
Figure ** f;
f; //
// OK
OK
.. .. ..
void
void showFigureInfo(
showFigureInfo( Figure
Figure && fp
fp )) //
// OK
OK
{{
.. .. ..
}}
Projektowanie obiektowe | 40
Klasy pochodne muszą dostarczyć definicji funkcji abstrakcyjnych
Figure
+double area() = 0
+double circumference() = 0
+const char * getName() = 0
Rectangle
Square
Triangle
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
+double area()
+double circumference()
+const char * getName()
―double height
―double width
―double side
―double base
―double width
Projektowanie obiektowe | 41
Klasy pochodne muszą dostarczyć definicji funkcji abstrakcyjnych
class
class Figure
Figure
{{
public
public ::
.. .. ..
virtual
virtual double
double
virtual
virtual double
double
.. .. ..
};
};
area()
area() const
const == 0;
0;
circumference()
circumference() const
const == 0;
0;
class
class Square:
Square: public
public Figure
Figure
{{
public
public ::
.. .. ..
Deklaracje
double
double area()
area() const;
const;
funkcji
double
double circumference()
circumference() const;
const;
.. .. ..
};
};
double
double Square::area()
Square::area() const
const
{{
return
return side
side ** side;
side;
}}
double
double Square::circumference()
Square::circumference() const
const
{{
return
return 44 ** side;
side;
}}
Deklaracje
funkcji
Projektowanie obiektowe | 42
Klasy abstrakcyjne
Java
Projektowanie obiektowe | 43
Figury płaskie raz jeszcze — klasa abstrakcyjna
abstract
abstract class
class Figure
Figure
{{
.. .. ..
}}
Figure
Figure == new
new Figure();
Figure(); //
// Nie
Nie tworzymy
tworzymy obiektów
obiektów klas
klas abstrakcyjnych
abstrakcyjnych
Klasa abstrakcyjna:
może posiadać implementację wybranych metod.
może posiadać metody abstrakcyjne.
może posiadać pola.
Nie tworzymy obiektów klas abstrakcyjnych.
Projektowanie obiektowe | 44
Figury płaskie raz jeszcze — klasa abstrakcyjna
abstract
abstract class
class Figure
Figure
{{
public
public Figure()
Figure() {}
{}
Metody
Metody abstrakcyjne.
abstrakcyjne. Muszą
Muszą zostać
zostać zdefiniowane
zdefiniowane w
w każdej
każdej
nieabstrakcyjnej
nieabstrakcyjnej klasie
klasie pochodnej.
pochodnej.
public
public abstract
abstract double
double area();
area();
public
public abstract
abstract double
double circumference();
circumference();
public
public String
String getName()
getName()
{{
return
return "Figura";
"Figura";
}}
}}
static
static int
int lbFigur;
lbFigur;
Zwykła
Zwykła metoda,
metoda, w
w klasach
klasach abstrakcyjnych
abstrakcyjnych mogą
mogą występować
występować
zarówno
zarówno metody
metody abstrakcyjne
abstrakcyjne jak
jak ii zwykłe.
zwykłe.
Metody abstrakcyjne deklarujemy z wykorzystaniem słowa kluczowego abstract.
Zapisujemy tylko sygnaturę metody, nie definiujemy jej ciała (nawet pustego).
Klasa
Klasa abstrakcyjna
abstrakcyjna pozwala
pozwala na
na częściowe
częściowe zdefiniowanie
zdefiniowanie czynności
czynności realizowanych
realizowanych przez
przez klasę,
klasę, pozwalając
pozwalając na
na
późniejsze
późniejsze uszczegółowienie
uszczegółowienie czynności
czynności reprezentowanych
reprezentowanych przez
przez metody
metody abstrakcyjne.
abstrakcyjne.
Projektowanie obiektowe | 45
Implementacja metody abstrakcyjne z klasie pochodnej
class
class Square
Square extends
extends Figure
Figure
{{
public
public Square()
Square()
{{
super();
super();
}}
@Override
@Override
public
public double
double area()
area()
{{
return
return side
side ** side;
side;
}}
}}
@Override
@Override
public
public double
double circumference()
circumference()
{{
return
return 44 ** side;
side;
}}
.. .. ..
Aby
Aby można
można było
było tworzyć
tworzyć obiekty
obiekty klasy
klasy pochodnej,
pochodnej, każda
każda metoda
metoda abstrakcyjna
abstrakcyjna musi
musi być
być w
w klasie
klasie pochodnej
pochodnej
zaimplementowana.
zaimplementowana. @Override
@Override –– adnotacja
adnotacja sygnalizująca
sygnalizująca intencję
intencję programisty
programisty przedefiniowania
przedefiniowania metody
metody
zz klasy
klasy bazowej,
bazowej, pozwala
pozwala na
na sprawdzenia
sprawdzenia w
w czasie
czasie kompilacji
kompilacji czy
czy programista
programista nie
nie popełnił
popełnił błędu,
błędu,
Projektowanie obiektowe | 46
Rola klas abstrakcyjnych ― częściowa definicja działania
abstract
abstract class
class Party
Party
{{
public
public enum
enum State
State {{ NONE,
NONE, BEFORE,
BEFORE, IN_PROGRESS,
IN_PROGRESS, AFTER
AFTER };
};
public
public Party()
Party() {{
state
state == State.NONE;
State.NONE;
}}
Działanie
Działanie tej
tej metody
metody zostanie
zostanie uszczegółowione
uszczegółowione
public
void
beforeParty()
{
public void beforeParty() {
w
w klasie
klasie pochodnej.
pochodnej.
state
state == State.BEFORE;
State.BEFORE;
}}
public
public abstract
abstract void
void makeParty();
makeParty();
public
public void
void afterParty()
afterParty() {{
state
state == State.AFTER;
State.AFTER;
}}
public
public void
void doParty()
doParty() {{
beforeParty();
beforeParty();
makeParty();
makeParty();
afterParty();
afterParty();
}}
}}
Ogólny
Ogólny przepis
przepis na
na realizację
realizację zachowania
zachowania obiektu
obiektu
―
― wymaga
wymaga doprecyzowania
doprecyzowania makeParty()
makeParty()
public
public State
State state;
state;
Projektowanie obiektowe | 47
Dziedziczenie z klas abstrakcyjnych ― doprecyzowanie działania
class
class GardenParty
GardenParty extends
extends Party
Party
{{
public
public GardenParty()
GardenParty()
{{
super();
super();
}}
@Override
@Override
public
public void
void
{{
state
state ==
}}
}}
makeParty()
makeParty()
State.IN_PROGRESS;
State.IN_PROGRESS;
prepareGrill();
prepareGrill();
for(
for( ;; ;; ))
openBeer();
openBeer();
public
public void
void prepareGrill()
prepareGrill() {}
{}
public
public void
void openBeer()
openBeer() {}
{}
Doprecyzowanie
Doprecyzowanie metody
metody abstrakcyjnej
abstrakcyjnej
Projektowanie obiektowe | 48
Klasy abstrakcyjne
C#
Projektowanie obiektowe | 49
Definicja klasy abstrakcyjnej w C# jest zbliżona do znanej z języka Java
Metody
Metody abstrakcyjne.
abstrakcyjne. Muszą
Muszą zostać
zostać zdefiniowane
zdefiniowane w
w każdej
każdej
nieabstrakcyjnej
nieabstrakcyjnej klasie
klasie pochodnej.
pochodnej.
abstract
abstract class
class Figure
Figure
{{
public
public Figure()
Figure() {}
{}
public
public abstract
abstract double
double area();
area();
public
public abstract
abstract double
double circumference();
circumference();
public
public String
String getName()
getName()
{{
return
return "Figura";
"Figura";
}}
}}
static
static int
int lbFigur;
lbFigur;
Mogą
Mogą występować
występować pola
pola
statyczne
statyczne ii niestatyczne
niestatyczne
Zwykła
Zwykła metoda,
metoda, w
w klasach
klasach abstrakcyjnych
abstrakcyjnych mogą
mogą występować
występować
zarówno
zarówno metody
metody abstrakcyjne
abstrakcyjne jak
jak ii zwykłe.
zwykłe.
Projektowanie obiektowe | 50
Definicja klasy pochodnej ― różnice w stosunku do języka Java
class
class Square
Square :: Figure
Figure
{{
public
public Square()
Square() :: base()
base()
{{
}}
public
public override
override
{{
return
return side
side
}}
}}
Przypomnienie
Przypomnienie ―
― dziedziczenie
dziedziczenie
przypomina
przypomina bardziej
bardziej C++
C++ niż
niż Javę
Javę
double
double area()
area()
** side;
side;
public
public override
override double
double circumference()
circumference()
{{
return
return 44 ** side;
side;
}}
.. .. ..
Każda
Każda metoda
metoda abstrakcyjna
abstrakcyjna musi
musi być
być w
w klasie
klasie pochodnej
pochodnej
zaimplementowana
zaimplementowana zz wykorzystaniem
wykorzystaniem słowa
słowa kluczowego
kluczowego override.
override.
Projektowanie obiektowe | 51
Rola klas abstrakcyjnych ― częściowa definicja działania
abstract
abstract class
class Party
Party
{{
public
public enum
enum State
State {{ NONE,
NONE, BEFORE,
BEFORE, IN_PROGRESS,
IN_PROGRESS, AFTER
AFTER };
};
public
public Party()
Party() {{
state
state == State.NONE;
State.NONE;
}}
Działanie
Działanie tej
tej metody
metody zostanie
zostanie uszczegółowione
uszczegółowione
public
void
beforeParty()
{
public void beforeParty() {
w
w klasie
klasie pochodnej.
pochodnej.
state
state == State.BEFORE;
State.BEFORE;
}}
public
public abstract
abstract void
void makeParty();
makeParty();
public
public void
void afterParty()
afterParty() {{
state
state == State.AFTER;
State.AFTER;
}}
public
public void
void doParty()
doParty() {{
beforeParty();
beforeParty();
makeParty();
makeParty();
afterParty();
afterParty();
}}
}}
Ogólny
Ogólny przepis
przepis na
na realizację
realizację zachowania
zachowania obiektu
obiektu
―
― wymaga
wymaga doprecyzowania
doprecyzowania makeParty()
makeParty()
public
public State
State state;
state;
Projektowanie obiektowe | 52
Interfejsy
Po co?
Projektowanie obiektowe | 53
Rola interfejsów w obiektowości
Klasa reprezentuje szablon, według którego tworzony będzie obiekt.
Klasa definiuje atrybuty i/lub metody w które taki obiekt będzie wyposażony.
Nawet klasa abstrakcyjna docelowo służy do definiowania obiektów.
Klasy
Klasy są
są narzędziem
narzędziem modelowania
modelowania ii definiowania
definiowania obiektów
obiektów w
w systemie.
systemie. Czasem
Czasem
potrzebujemy
potrzebujemy modelować
modelować potencjalne
potencjalne zachowania
zachowania obiektów,
obiektów, często
często w
w oderwaniu
oderwaniu od
od
nich
nich samych.
samych. Do
Do modelowania
modelowania zachowań
zachowań wykorzystujemy
wykorzystujemy interfejsy.
interfejsy.
Interfejsy nie są po to, by definiować obiekty.
Interfejsy są po to, by definiować zestaw zachowań.
Obiekt pewnej klasy może implementować interfejs — realizować zachowania
określone przez dany interfejs.
Interfejsy zazwyczaj nie zawierają pól.
Projektowanie obiektowe | 54
Rola interfejsów w obiektowości
Gdy pewna klasa wykorzystuje interfejs, to oznacza, że gwarantuje obsługę metody
zadeklarowanej w tym interfejsie.
Metody interfejsu deklaruje się bez żadnej treści, konkretna definicja metody
w interfejsie nie jest dozwolona.
Interfejs przypomina nieco klasę abstrakcyjną, posiadającą wszystkie metody
abstrakcyjne.
Klasy abstrakcyjne bywają wykorzystywane –np. w C++ – do realizacji interfejsów nie
występujących jawnie w języku.
Ważna różnica – klasa pochodna abstrakcyjnej klasy bazowej może zmieniać
widoczność metod odziedziczonych, jeśli jakaś klasa implementuje interfejs, to
wówczas musi udostępniać wszystkie metody zdefiniowane w tym interfejsie.
Projektowanie obiektowe | 55
Interfejsy w C++
W języku C++ nie występuje osobna notacja dla interfejsów.
Wykorzystanie koncepcji interfejsów wymaga zastosowania klas zawierających funkcje
abstrakcyjne.
Występujące w C++ dziedziczenie wielobazowe pozwala na swobodne budowanie klas
posiadających wiele bezpośrednich klas bazowych.
Interfejsy występują zazwyczaj w językach nie oferujących dziedziczenia
wielobazowego.
Jawnie wodrębnione interfejsy występują w językach Java, C#, PHP.
Niektóre
Niektóre kompilatory
kompilatory (np.
(np. VC++)
VC++) wprowadzają
wprowadzają własne
własne rozszerzenia
rozszerzenia do
do C++
C++ oferujące
oferujące
mechanizm
mechanizm podobny
podobny do
do interfejsów
interfejsów zz języka
języka Java
Java ii C#.
C#.
Projektowanie obiektowe | 56
Interfejsy
Java
Projektowanie obiektowe | 57
Przykłady interfejsu
interface
interface BasicCalculations
BasicCalculations
{{
double
double area();
area();
double
double circumference();
circumference();
}}
Domyślnie
Domyślnie publicznie
publicznie
ii abstrakcyjne
abstrakcyjne
Wszystkie metody interfejsu są domyślnie publiczne i abstrakcyjne.
Metody interfejsów nie mogą być statyczne (static) ani zakończone (final).
interface
interface GearBoxActions
GearBoxActions
{{
int
int gearUp();
gearUp();
int
int gearDown();
gearDown();
}}
int
int numberOfGears
numberOfGears == 6;
6;
Domyślnie
Domyślnie publiczne,
publiczne, statyczne
statyczne ii zakończone.
zakończone.
Interfejs może zawierać pola, są one wtedy domyślnie publiczne, statyczne i finalne.
Wszystkie klasy, które kiedyś zaimplementują interfejs, będą miały zawsze bezpośredni
dostęp do tych samych, stałych wartości pól.
Projektowanie obiektowe | 58
Przykłady wykorzystania interfejsu
class
class Driver
Driver implements
implements GearBoxActions
GearBoxActions
{{
public
public Driver()
Driver() {}
{}
@Override
@Override
public
public int
int gearUp()
gearUp()
{{
if(
if( currentGear
currentGear << numberOfGears
numberOfGears
++currentGear;
++currentGear;
return
return currentGear;
currentGear;
}}
@Override
@Override
public
public int
int gearDown()
gearDown()
{{
if(
if( currentGear
currentGear >> 00 ))
--currentGear;
--currentGear;
return
return currentGear;
currentGear;
}}
}}
Klasa
Klasa implementuje
implementuje dany
dany
interfejs
interfejs
))
Implementacje
Implementacje metod
metod interfejsu
interfejsu
muszą
muszą być
być publiczne
publiczne
public
public int
int currentGear
currentGear == 0;
0;
Projektowanie obiektowe | 59
Implementacja wielu interfejsów
Interfejs definiujący akcje kontrolujące skrzynię biegów.
interface
interface GearBoxActions
GearBoxActions
{{
int
int gearUp();
gearUp();
int
int gearDown();
gearDown();
}}
Interfejs definiujący akcje kontrolujące prędkość (hamulec, przyśpieszanie).
interface
interface SpeedControl
SpeedControl
{{
void
void pressBreak();
pressBreak();
void
void releaseBreak();
releaseBreak();
}}
void
void accelerate();
accelerate();
void
void slowDown();
slowDown();
Projektowanie obiektowe | 60
Implementacja wielu interfejsów
class
class Driver
Driver implements
implements GearBoxActions,
GearBoxActions, SpeedControl
SpeedControl
{{
public
public Driver()
Driver() {}
{}
@Override
@Override
public
public int
int gearUp()
gearUp() {{ …… }}
Implementacja
Implementacja dwóch
dwóch interfejsów
interfejsów
@Override
@Override
public
public int
int gearDown()
gearDown() {{ …… }}
@Override
@Override
public
public void
void pressBreak()
pressBreak() {{ …… }}
@Override
@Override
public
public void
void releaseBreak()
releaseBreak() {{ …… }}
@Override
@Override
public
public void
void accelerate()
accelerate() {{ …… }}
}}
@Override
@Override
public
public void
void slowDown()
slowDown() {{ …… }}
Projektowanie obiektowe | 61
Implementacja interfejsów, zasady ogólne
Nieabstrakcyjna klasa implementująca interfejs:
Musi posiadać implementację wszystkich metod interfejsu,
Implementowane metody muszą zachować sygnaturę metod z interfejsu,
Implementowane metody muszą być zdefiniowane jako publiczne.
Klasa może implementować więcej niż jeden interfejs.
Nie można definiować konstruktora i destruktora wewnątrz interfejsu.
Interfejsy zwyczajowo definiuje rozpoczynając ich nazwę od litery „I”.
interface
interface IGearBoxActions
IGearBoxActions
{{
.. .. ..
}}
interface
interface ISpeedControl
ISpeedControl
{{
.. .. ..
}}
Projektowanie obiektowe | 62
Rozszerzanie interfejsów
interface
interface SpeedControl
SpeedControl
{{
void
void pressBreak();
pressBreak();
void
void releaseBreak();
releaseBreak();
}}
void
void accelerate();
accelerate();
void
void slowDown();
slowDown();
interface
interface BoostedSpeedControl
BoostedSpeedControl extends
extends SpeedControl
SpeedControl
{{
void
void nitroBoosterOn();
nitroBoosterOn();
void
void nitroBoosterOff();
nitroBoosterOff();
}}
Interfejsy mogą rozszerzać inne interfejsy.
Interfejs nie może implementować innego interfejsu.
Projektowanie obiektowe | 63
Rozszerzać można wiele interfejsów
interface
interface GearBoxActions
GearBoxActions
{{
int
int gearUp();
gearUp();
int
int gearDown();
gearDown();
}}
interface
interface SpeedControl
SpeedControl
{{
void
void pressBreak();
pressBreak();
void
void releaseBreak();
releaseBreak();
}}
void
void
void
void
accelerate();
accelerate();
slowDown();
slowDown();
interface
interface BasicDriverActions
BasicDriverActions extends
extends GearBoxActions,
GearBoxActions, SpeedControl
SpeedControl
{{
void
void start();
start();
void
void stop();
stop();
}}
Należy
Należy pamiętać,
pamiętać, że
że ostatecznie
ostatecznie jakaś
jakaś klasa
klasa musi
musi zaimplementować
zaimplementować metody
metody
zadeklarowane
zadeklarowane w
w poszczególnych
poszczególnych interfejsach.
interfejsach.
Projektowanie obiektowe | 64
Implementacja złożonych interfejsów
class
class BasicDriver
BasicDriver implements
implements BasicDriverActions
BasicDriverActions {{
@Override
@Override
public
public int
int gearUp()
gearUp() {{ …… }}
@Override
@Override
public
public int
int gearDown()
gearDown() {{ …… }}
@Override
@Override
public
public void
void pressBreak()
pressBreak() {{ …… }}
@Override
@Override
public
public void
void releaseBreak()
releaseBreak() {{ …… }}
@Override
@Override
public
public void
void accelerate()
accelerate() {{ …… }}
@Override
@Override
public
public void
void slowDown()
slowDown() {{ …… }}
@Override
@Override
public
public void
void start()
start() {{ …… }}
}}
@Override
@Override
public
public void
void stop()
stop() {{ …… }}
Elastycznym
Elastycznym ii wygodnym
wygodnym rozwiązaniem
rozwiązaniem
jest
jest połączenie
połączenie koncepcji
koncepcji interfejsów
interfejsów ii
klas
klas abstrakcyjnych.
abstrakcyjnych.
Projektowanie obiektowe | 65
Klasy abstrakcyjne + interfejsy
abstract
abstract class
class BasicDriver
BasicDriver implements
implements SpeedControl
SpeedControl {{
public
public String
String nickName
nickName == "";
"";
public
public boolean
boolean avaliable
avaliable == true;
true;
}}
class
class Driver
Driver extends
extends BasicDriver
BasicDriver implements
implements GearBoxActions
GearBoxActions
@Override
@Override
public
public int
int gearUp()
gearUp() {{ …… }}
{{
@Override
@Override
public
public int
int gearDown()
gearDown() {{ …… }}
@Override
@Override
public
public void
void pressBreak()
pressBreak() {{ …… }}
@Override
@Override
public
public void
void releaseBreak()
releaseBreak() {{ …… }}
@Override
@Override
public
public void
void accelerate()
accelerate() {{ …… }}
}}
@Override
@Override
public
public void
void slowDown()
slowDown() {{ …… }}
Projektowanie obiektowe | 66
Nie zawsze klasy abstrakcyjne są użyteczne
class
class BasicDriver
BasicDriver implements
implements
@Override
@Override
public
public void
void pressBreak()
pressBreak()
SpeedControl
SpeedControl {{
{{ …… }}
@Override
@Override
public
public void
void releaseBreak()
releaseBreak() {{ …… }}
@Override
@Override
public
public void
void accelerate()
accelerate() {{ …… }}
@Override
@Override
public
public void
void slowDown()
slowDown() {{ …… }}
}}
public
public
public
public
String
String nickName
nickName ==
boolean
boolean avaliable
avaliable
"";
"";
== true;
true;
class
class Driver
Driver extends
extends BasicDriver
BasicDriver implements
implements GearBoxActions
GearBoxActions
@Override
@Override
public
public int
int gearUp()
gearUp() {{ …… }}
}}
{{
@Override
@Override
public
public int
int gearDown()
gearDown() {{ …… }}
Projektowanie obiektowe | 67
Jeszcze raz przykład z figurami
abstract
abstract class
class Figure
Figure implements
implements BasicCalculations
BasicCalculations
{{
public
public Figure()
Figure() {}
{}
}}
public
public String
String getName()
getName()
{{
return
return "Figura";
"Figura";
}}
Klasa
Klasa abstrakcyjne
abstrakcyjne wykorzystuje
wykorzystuje
interfejs,
interfejs, ale
ale może
może go
go w
w rzeczywistości
rzeczywistości nie
nie
implementować
implementować —
— musi
musi to
to zrobić
zrobić pierwsza
pierwsza
nieabstrakcyjna
nieabstrakcyjna klasa
klasa pochodna
pochodna
Abstrakcyjna klasa słowem kluczowym implements sygnalizuje, że wykorzystuje
określony interfejs, choć w istocie może go nie implementować w pełni.
Obowiązek implementacji metod interfejsu spada na klasy pochodne, każda
nieabstrakcyjna klasa pochodna musi posiadać pełną implementację interfejsu.
Projektowanie obiektowe | 68
Jeszcze raz przykład z figurami
class
class Square
Square extends
extends Figure
Figure
{{
public
public Square()
Square()
{{
super();
super();
}}
Dziedziczenie
Dziedziczenie po
po klasie
klasie abstrakcyjnej,
abstrakcyjnej,
która
która nie
nie zdefiniowała
zdefiniowała interfejsu,
interfejsu,
wymagana
wymagana implementacja
implementacja wszystkich
wszystkich
metod
metod interfejsu
interfejsu
@Override
@Override
public
public double
double area()
area()
{{
return
return side
side ** side;
side;
}}
@Override
@Override
public
public double
double circumference()
circumference()
{{
return
return 44 ** side;
side;
}}
}}
public
public int
int side
side == 0;
0;
Projektowanie obiektowe | 69
Jeszcze raz przykład z figurami
class
class Circle
Circle extends
extends Figure
Figure
{{
public
public Circle()
Circle()
{{
super();
super();
}}
@Override
@Override
public
public double
double area()
area()
{{
return
return Math.PI
Math.PI ** radius
radius ** radius;
radius;
}}
@Override
@Override
public
public double
double circumference()
circumference()
{{
return
return 22 ** Math.PI
Math.PI ** radius;
radius;
}}
}}
public
public int
int radius
radius == 0;
0;
Projektowanie obiektowe | 70
Jeszcze raz przykład z figurami — interfejs jako obiekt
class
class FigDemoInterface
FigDemoInterface
{{
public
public static
static void
void
{{
.. .. ..
Square
Square ss == new
new
Circle
Circle cc == new
new
}}
}}
main(String[]
main(String[] args)
args)
Square();
Square();
Circle();
Circle();
BasicCalculations
BasicCalculations calc
calc == s;
s;
System.out.println(
System.out.println( calc.area()
calc.area() );
);
calc
calc == c;
c;
System.out.println(
System.out.println( calc.area()
calc.area() );
);
.. .. ..
Uaktywnienie
Uaktywnienie zachowania
zachowania obiektu
obiektu za
za pośrednictwem
pośrednictwem obiektu
obiektu interfejsowego
interfejsowego ––
Projektowanie obiektowe | 71
Jeszcze raz przykład z figurami — funkcja bazująca na zachowaniu
class
class FigDemoInterface
FigDemoInterface
{{
public
public static
static void
void calcAndPrintAreas(
calcAndPrintAreas( BasicCalculations
BasicCalculations bc
bc ))
{{
System.out.println(
System.out.println( bc.area()
bc.area() );
);
}}
public
public static
static void
void
{{
.. .. ..
Square
Square ss == new
new
Circle
Circle cc == new
new
}}
}}
main(String[]
main(String[] args)
args)
Square();
Square();
Circle();
Circle();
calcAndPrintAreas(
calcAndPrintAreas(
calcAndPrintAreas(
calcAndPrintAreas(
.. .. ..
ss
cc
);
);
);
);
Interfejsy
Interfejsy pozwalają
pozwalają na
na tworzenie
tworzenie kodu
kodu bazującego
bazującego na
na zachowaniu
zachowaniu obiektów
obiektów aa nie
nie na
na
ich
ich typie
typie —
— przynależności
przynależności do
do zadanej
zadanej hierarchii
hierarchii klas.
klas.
Projektowanie obiektowe | 72
Interfejsy
C#
Właściwe podobnie jak w języku Java...
Projektowanie obiektowe | 73
Interfejsy w C# — podstawowe informacje
Nie można definiować pól w interfejsie, nawet pól statycznych.
Nie można definiować konstruktora i destruktora wewnątrz interfejsu.
Wszystkie metody interfejsu są publiczne.
Interfejs może zawierać właściwości.
Nie można zagnieździć klasy, struktury, typów wyliczeniowych i innych interfejsów
wewnątrz interfejsu.
Interfejs nie może dziedziczyć po klasie.
Interfejs może dziedziczyć zachowanie po innym interfejsie.
Projektowanie obiektowe | 74
Interfejsy w C# — podstawowe informacje
class
class Driver
Driver :: GearBoxActions
GearBoxActions
{{
public
public Driver()
Driver() {}
{}
public
public int
int gearUp()
gearUp()
{{
if(
if( currentGear
currentGear << numberOfGears
numberOfGears
++currentGear;
++currentGear;
return
return currentGear;
currentGear;
}}
Klasa
Klasa implementuje
implementuje dany
dany
interfejs
interfejs
))
public
public int
int gearDown()
gearDown()
{{
if(
if( currentGear
currentGear >> 00 ))
--currentGear;
--currentGear;
return
return currentGear;
currentGear;
}}
}}
public
public int
int currentGear
currentGear == 0;
0;
Projektowanie obiektowe | 75

Podobne dokumenty