Podstawy programowania obiektowego

Transkrypt

Podstawy programowania obiektowego
Podstawy programowania obiektowego
wykład 5 – dziedziczenie i wyjątki
•
•
•
•
•
Dziedziczenie jest sposobem na utworzenie nowej klasy na podstawie klasy już istniejącej.
Dziedziczenie tworzy szczególną (bardziej specyficzną) odmianę klasy istniejącej.
Klasa istniejąca (z której dziedziczymy) nazywana jest klasą bazową.
Nowa klasa (utworzona przez dziedziczenie) nazywana jest klasą pochodną.
Relacja między klasą istniejącą (bardziej ogólną), a nową (specjalizowaną) wyraża się
poprzez sformułowania:
◦ <klasa pochodna> jest szczególnym przypadkiem <klasy bazowej>
◦ <klasa pochodna> jest rodzajem <klasy bazowej>
◦ <klasa bazowa> jest uogólnieniem <klasy pochodnej>
1/41
•
Np.:
◦ Koło jest szczególnym przypadkiem figury
◦ Przesyłka polecona jest rodzajem przesyłki pocztowej
◦ Osoba jest uogólnieniem pracownika
class Osoba { }
class Figura { }
class PrzesyłkaPocztowa { }
class Pracownik : Osoba { }
class Koło : Figura { }
class PrzesyłkaPolecona : PrzesyłkaPocztowa { }
2/41
•
Klasa pochodna dziedziczy pola i metody klasy bazowej:
class Osoba
{
public string imię;
public string nazwisko;
public override string ToString() {
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba
{
// pusto
}
3/41
Klasa pochodna dziedziczy pola i metody klasy bazowej:
•
class Osoba {
public string imię;
public string nazwisko;
public override string ToString() { return ... }
}
class Pracownik : Osoba {
// pusto
}
class Program {
static void Main(string[] args) {
Osoba os = new Osoba();
Jan Kowalski
os.imię = "Jan";
os.nazwisko = "Kowalski";
Console.WriteLine(os.ToString());
Pracownik pr = new Pracownik();
pr.imię = "Piotr";
pr.nazwisko = "Nowak";
Console.WriteLine(pr.ToString());
Piotr Nowak
}
}
4/41
•
Klasa pochodna może dodać własne pola:
class Osoba {
public string imię;
public string nazwisko;
public override string ToString() {
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba {
public double płaca;
}
5/41
Klasa pochodna może dodać własne pola:
•
class Osoba {
public string imię;
public string nazwisko;
public override string ToString() {
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba {
public double płaca;
}
class Program {
static void Main(string[] args) {
Pracownik pr = new Pracownik();
pr.imię = "Piotr";
pr.nazwisko = "Nowak";
Console.WriteLine(pr.ToString());
Piotr Nowak
pr.płaca = 2200.0;
}
}
6/41
•
Klasa pochodna może dodać własne metody:
class Osoba {
public string imię;
public string nazwisko;
public override string ToString() {
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba {
public double płaca;
public void Wypłać() {
Console.WriteLine(imię + " " + nazwisko +
" otrzymuje " + płaca + "zł");
}
}
7/41
Klasa pochodna może dodać własne metody:
•
class Osoba {
...
}
class Pracownik : Osoba {
public double płaca;
public void Wypłać() {
Console.WriteLine(imię + " " + nazwisko +
" otrzymuje " + płaca + "zł");
}
}
class Program {
static void Main(string[] args) {
Pracownik pr = new Pracownik();
Piotr Nowak
pr.imię = "Piotr";
pr.nazwisko = "Nowak";
Console.WriteLine(pr.ToString());
pr.płaca = 2200.0;
pr.Wypłać();
}
}
Piotr Nowak otrzymuje 2200.0 zł
8/41
•
Klasa pochodna może nadpisać (przesłonić) metodę klasy bazowej:
class Osoba {
...
public override string ToString() {
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba {
...
public override string ToString() {
return imię + " " + nazwisko + ": " + płaca;
}
}
class Program {
static void Main(string[] args) {
Pracownik pr = new Pracownik();
pr.imię = "Piotr";
Piotr Nowak: 2200.0
pr.nazwisko = "Nowak";
pr.płaca = 2200.0;
Console.WriteLine(pr.ToString());
}
}
9/41
•
•
•
Klasa pochodna może nadpisać (przesłonić) metodę klasy bazowej.
Poprzednia (odziedziczona) wersja metody nadal jest dostępna.
Można się do niej odwołać przy pomocy słowa kluczowego base.
class Osoba {
...
public override string ToString()
{
return imię + " " + nazwisko;
}
}
class Pracownik : Osoba {
public override string ToString()
{
return base.ToString() + ": " + płaca;
}
}
10/41
•
Do odziedziczonej wersji metody można się odwołać przy pomocy słowa
kluczowego base.
class Osoba {
...
}
class Pracownik : Osoba {
public double płaca;
public void Wypłać() { ... }
public override string ToString() {
return base.ToString() + ": " + płaca;
}
}
class Program {
static void Main(string[] args) {
Pracownik pr = new Pracownik();
pr.imię = "Piotr";
Piotr Nowak: 2200.0
pr.nazwisko = "Nowak";
pr.płaca = 2200.0;
Console.WriteLine(pr.ToString());
}
}
11/41
•
Klasa pochodna nie dziedziczy konstruktorów z klasy bazowej!
class Osoba {
public string imię;
public string nazwisko;
public Osoba(string imię, string nazwisko) {
this.imię = imię;
this.nazwisko = nazwisko;
}
public override string ToString() { ... }
}
class Pracownik : Osoba {
public double płaca;
...
}
class Program {
static void Main(string[] args) {
Osoba os = new Osoba("Jan", "Kowalski");
Błąd!
Pracownik pr = new Pracownik("Piotr", "Nowak");
}
}
12/41
•
Klasa pochodna nie dziedziczy konstruktorów z klasy bazowej.
class Osoba {
public string imię;
public string nazwisko;
public Osoba(string imię, string nazwisko) { ... }
public override string ToString() { ... }
}
Musimy dostarczyć
class Pracownik : Osoba {
własny konstruktor
public double płaca;
public Pracownik(string im, string nazw, double płaca) {
this.imię = im;
this.nazwisko = nazw;
this.płaca = płaca;
}
...
}
class Program {
Nadal błąd?
static void Main(string[] args) {
Osoba os = new Osoba("Jan", "Kowalski");
Pracownik pr = new Pracownik("Piotr", "Nowak", 2200.0);
}
}
13/41
Klasa pochodna to „klasa bazowa plus dodatki” (nowe pola, nowe metody).
Obiekt klasy pochodnej jest to „obiekt klasy bazowej z dodatkami” (z nowymi
polami, nowymi metodami).
• „Wewnątrz” obiektu klasy Pracownik znajduje się obiekt klasy Osoba.
• W momencie tworzenia obiektu klasy Pracownik najpierw musi być stworzony
obiekt klasy Osoba:
Pracownik
pr
class Osoba {
public Osoba() {
Osoba
Console.WriteLine("Tworzę osobę.");
imię
}
nazwisko
...
Osoba(...)
}
ToString()
class Pracownik : Osoba {
płaca
public Pracownik() {
Pracownik(...)
Console.WriteLine("Tworzę pracownika.");
Wypłać()
}
ToString()
...
•
•
}
...
Tworzę osobę.
Tworzę pracownika.
Pracownik pr = new Pracownik();
14/41
•
•
•
W momencie tworzenia obiektu klasy pochodnej najpierw musi być wywołany
konstruktor klasy bazowej.
Jeśli konstruktor przyjmuje jakieś parametry, należy mu je przekazać.
Służy do tego słowo kluczowe base.
Pracownik
this
base
Osoba
class Osoba {
public string imię;
imię
public string nazwisko;
nazwisko
public Osoba(string imię, string nazwisko) {
Osoba(...)
this.imię = imię;
toString()
this.nazwisko = nazwisko;
płaca
}
Pracownik(...)
}
wypłać()
class Pracownik : Osoba {
toString()
public double płaca;
public Pracownik(string im, string nazw, double płaca):
base(imię, nazwisko) {
this.płaca = płaca;
}
}
...
Pracownik pr = new Pracownik("Piotr", "Nowak", 2200.0);
15/41
•
Przykład: konto z oprocentowaniem.
class Konto {
private double suma;
public Konto(double wkład) { suma = wkład; }
public void Wpłać(double kwota) { suma += kwota; }
public void Wypłać(double kwota) { suma -= kwota; }
public double Saldo() { return suma; }
}
class KontoOprocentowane : Konto {
private double procent;
public KontoOprocentowane(double wkład, double proc)
: base(wkład) {
procent = proc;
}
public void NaliczProcent() {
suma += suma * procent;
}
}
Błąd!
16/41
•
Przykład: konto z oprocentowaniem.
class Konto {
protected double suma;
poza{dostępem
public Konto(double wkład)
suma =publicznym
wkład; i}
do += kwota; }
public void Wpłać(double prywatnym,
kwota) {mamy
suma
dyspozycji
również
dostęp
public void Wypłać(double kwota) { suma
-= kwota; }
chroniony
(składowe
public double Saldo() { return suma; } dostępne
dla klas pochodnych)
}
class KontoOprocentowane : Konto {
private double procent;
public KontoOprocentowane(double wkład, double proc)
: base(wkład) {
procent = proc;
}
public void NaliczProcent() {
suma += suma * procent;
}
}
17/41
•
Przykład: konto z oprocentowaniem.
class Program
{
static void Main(string[] args)
{
KontoOprocentowane konto;
konto = new KontoOprocentowane(1000.0, 0.05);
konto.Wpłać(200.0);
konto.NaliczProcent();
konto.Wypłać(500.0);
Console.WriteLine(konto.Saldo());
}
}
760.0
18/41
•
Klasa pochodna dziedziczy nie tylko pola i metody, ale również typ.
class Osoba {
protected String imię;
protected String nazwisko;
}
class Pracownik : Osoba {
protected double płaca;
public void wypłać() { /*...*/ }
}
•
•
•
•
Klasa Pracownik dziedziczy z klasy Osoba, a zatem
Pracownik jest szczególnym rodzajem Osoby, a zatem Rzutowanie w górę:
Pracownik jest Osobą.
Tworzymy Pracownika,
ale traktujemy go jako
Osobę.
Co z tego wynika?
Osoba os = new Pracownik("Piotr", "Nowak", 2200.0);
os.wypłać();
19/41
•
Klasa pochodna dziedziczy nie tylko pola i metody, ale również typ.
class Osoba {
protected String imię;
protected String nazwisko;
}
class Pracownik : Osoba {
protected double płaca;
public void wypłać() { /*...*/ }
}
Osoba os = new Pracownik("Piotr", "Nowak", 2200.0);
os.wypłać();
//...
Pracownik pr = (Pracownik)os;
pr.wypłać();
Rzutowanie w dół:
Tę Osobę potraktujemy
jako Pracownika (to
działa, gdyż faktycznie
jest to Pracownik).
20/41
•
Klasa pochodna dziedziczy nie tylko pola i metody, ale również typ.
class Osoba {
protected String imię;
protected String nazwisko;
}
class Pracownik : Osoba {
protected double płaca;
public void wypłać() { /*...*/ }
}
Osoba os = new Pracownik("Piotr", "Nowak", 2200.0);
os.wypłać();
//...
if(os is Pracownik) {
Pracownik pr = (Pracownik)os;
pr.wypłać();
}
Rzutowanie w dół:
Bezpieczniejsze rozwiązanie –
sprawdzenie faktycznego typu
obiektu
21/41
•
Klasa pochodna dziedziczy nie tylko pola i metody, ale również typ.
class Osoba {
protected String imię;
protected String nazwisko;
}
class Pracownik : Osoba {
protected double płaca;
public void wypłać() { /*...*/ }
}
Osoba os = new Pracownik("Piotr", "Nowak", 2200.0);
os.wypłać();
//...
Pracownik pr = os as Pracownik;
if (pr != null)
Rzutowanie w dół:
{
Jeszcze inne rozwiązanie
pr.wypłać();
}
null oznacza
„brak obiektu pod
tą referencją”
22/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
class Figura
{
// ...
public void rysuj() {
}
class Trójkąt : Figura
{
// ...
public void rysuj() {
}
class Koło : Figura
{
// ...
public void rysuj() {
}
class Prostokąt : Figura
{
// ...
public void rysuj() {
}
Console.WriteLine("rysuję figurę"); }
Console.WriteLine("rysuję trójkąt"); }
Console.WriteLine("rysuję koło"); }
Console.WriteLine("rysuję prostokąt"); }
23/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
figury[0] = new Koło();
figury[1] = new Trójkąt();
figury[2] = new Prostokąt();
// ...
}
24/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
figury[0] = new Koło();
figury[1] = new Trójkąt();
figury[2] = new Prostokąt();
// ...
figury[1].rysuj();
}
Pytanie: jaka figura się
narysuje?
która metoda rysuj() się
wywoła?
rysuję figurę
class Figura
{
// ...
public void rysuj() {
Console.WriteLine("rysuję figurę");
}
}
25/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
figury[0] = new Koło();
figury[1] = new Trójkąt();
figury[2] = new Prostokąt();
// ...
(figury[1] as Trójkąt).rysuj();
// lub:
((Trójkąt)figury[1]).rysuj();
}
Rzutowanie w dół pozwala
dostać się do rzeczywistego
obiektu ukrytego w tablicy.
rysuję trójkąt
26/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
figury[0] = new Koło();
figury[1] = new Trójkąt();
figury[2] = new Prostokąt();
Skąd jednak mamy
// ...
pewność, że to faktycznie
trójkąt?
}
Random rnd = new Random();
for (int i = 0; i < figury.Length; ++i)
{
switch (rnd.Next(3))
{
case 0: figury[i] = new Koło(); break;
case 1: figury[i] = new Trójkąt(); break;
case 2: figury[i] = new Prostokąt(); break;
}
}
27/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
figury[0] = new Koło();
figury[1] = new Trójkąt();
figury[2] = new Prostokąt();
Skąd jednak mamy
// ...
pewność, że to faktycznie
trójkąt?
}
for (int i = 0; i < figury.Length; ++i)
{
Console.WriteLine("Wybierz rodzaj figury: ...");
int wybór = int.Parse(Console.ReadLine());
switch (wybór)
{
case 0: figury[i] = new Koło(); break;
case 1: figury[i] = new Trójkąt(); break;
case 2: figury[i] = new Prostokąt(); break;
}
}
28/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
//
// wypełnianie tablicy figur...
//
if (figury[1] is Trójkąt)
((Trójkąt)figury[1]).rysuj();
else if (figury[1] is Koło)
((Koło)figury[1]).rysuj();
else if (figury[1] is Prostokąt)
((Prostokąt)figury[1]).rysuj();
}
Operator is sprawdza faktyczny
typ obiektu w tablicy.
29/41
•
•
Do czego może się to przydać?
Dzięki temu możemy traktować w jednakowy sposób wiele obiektów należących
do jednej rodziny (hierarchii) – określane jest to jako polimorfizm.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
//
Operację możemy wykonać dla
// wypełnianie tablicy figur...
każdego elementu tablicy.
//
foreach (Figura figura in figury)
{
if (figura is Trójkąt)
((Trójkąt)figura).rysuj();
else if (figura is Koło)
((Koło)figura).rysuj();
else if (figura is Prostokąt)
((Prostokąt)figura).rysuj();
}
}
Operator is sprawdza faktyczny
typ obiektu w tablicy.
30/41
•
Jeśli oznaczymy metodę jako wirtualną, program sam (automatycznie) będzie
dokonywał powyższych sprawdzeń i rzutowań w momencie jej wywołania.
class Figura
{
// ...
public virtual void rysuj() { ... }
}
class Trójkąt : Figura
{
// ...
public override void rysuj() { ... }
}
class Koło : Figura
{
// ...
public override void rysuj() { ... }
}
class Prostokąt : Figura
{
// ...
public override void rysuj() { ... }
}
Przesłaniając metodę wirtualną
dodajemy słowo kluczowe
override, wskazujące, że
chcemy nadpisać wersję z klasy
bazowej
31/41
•
•
Jeśli oznaczymy metodę jako wirtualną, program sam (automatycznie) będzie
dokonywał powyższych sprawdzeń i rzutowań w momencie jej wywołania.
Teraz poniższy kod będzie działał prawidłowo.
static void Main(string[] args)
{
Figura[] figury = new Figura[10];
//
// wypełnianie tablicy figur...
//
foreach (Figura figura in figury)
{
figura.rysuj();
}
}
Zawsze wywoła się wersja
metody rysuj() z odpowiedniej
klasy.
32/41
•
Aby dziedziczyć z klasy, programista nie musi znać jej implementacji.
class Zbiór {
// ...
public void Dodaj(int wartość) { /* ... */ }
public void Usuń(int wartość) { /* ... */ }
public bool CzyJest(int wartość) { /* ... */ }
}
class ZbiórParzysty : Zbiór {
public void Dodaj(int wartość) {
if (wartość % 2 == 0)
base.dodaj(wartość);
}
}
class Program {
static void Main(string[] args) {
ZbiórParzysty zbiór = new ZbiórParzysty();
zbiór.Dodaj(4);
false
zbiór.Dodaj(5);
zbiór.Dodaj(6);
Console.WriteLine(zbiór.CzyJest(5));
}
}
33/41
•
To samo dotyczy kompozycji.
class Plik {
//...
public Plik(string nazwa) { /* ... */ }
public void Zapisz(string tekst) { /* ... */ }
public string Odczytaj() { /* ... */ }
public void Zamknij() { /* ... */ }
}
class MaszynaSzyfrująca {
//...
public MaszynaSzyfrująca(int klucz) { /* ... */ }
public string Szyfruj(string tekst) { /* ... */ }
public string Odszyfruj(string tekst) { /* ... */ }
}
class PlikSzyfrowany : Plik {
private MaszynaSzyfrująca ms;
public PlikSzyfrowany(string nazwa, int klucz)
: base(nazwa)
{
ms = new MaszynaSzyfrująca(klucz);
}
}
34/41
•
To samo dotyczy kompozycji.
class PlikSzyfrowany : Plik
{
private MaszynaSzyfrująca ms;
public PlikSzyfrowany(string nazwa, int klucz)
: base(nazwa)
{
ms = new MaszynaSzyfrująca(klucz);
}
public void Zapisz(string tekst)
{
string zaszyfrowany = ms.Szyfruj(tekst);
base.Zapisz(zaszyfrowany);
}
public string Odczytaj()
{
string zaszyfrowany = base.Odczytaj();
string tekst = ms.Odszyfruj(zaszyfrowany);
return tekst;
}
}
35/41
class Program {
static void Main(string[] args) {
PlikSzyfrowany plik;
plik = new PlikSzyfrowany("dane.bin", 1234);
plik.Zapisz("Ala ma kota.");
plik.Zamknij();
Console.ReadKey();
}
}
36/41
•
•
Jak powiadamiać o błędach?
Może wypisać komunikat?
class Konto
{
// ...
public void wypłać(double kwota)
{
if (kwota <= 0)
Console.WriteLine("Błędna kwota");
else if (kwota > suma)
Console.WriteLine("Brak środków");
else
suma -= kwota;
}
}
•
Komunikat to informacja dla użytkownika, ale jak powiadomić programistę, aby
mógł zareagować na błąd?
37/41
•
•
Jak powiadamiać o błędach?
Może wypisać komunikat?
class Konto
{
// ...
public void wypłać(double kwota) { ... }
}
•
Komunikat to informacja dla użytkownika, ale jak powiadomić programistę, aby
mógł zareagować na błąd?
static void Main(string[] args)
{
// wykonywanie przelewu
Konto k1 = new Konto(), k2 = new Konto();
// ...
double kwota = double.Parse(Console.ReadLine());
k1.wypłać(kwota);
k2.wpłać(kwota);
Jeśli był błąd przy
}
wypłacie, nie
powinniśmy wpłacać!
38/41
•
•
Jak powiadamiać o błędach?
W programowaniu obiektowym preferowanym rozwiązaniem są wyjątki.
class Konto {
Wystąpienie wyjątku
// ...
powoduje
public void wypłać(double kwota)
zakończenie metody.
{
if (kwota <= 0)
throw new Exception("Błędna kwota");
if (kwota > suma)
throw new Exception("Brak środków");
suma -= kwota;
}
}
static void Main(string[] args) {
// wykonywanie przelewu
Konto k1 = new Konto(), k2 = new Konto();
// ...
double kwota = double.Parse(Console.ReadLine());
k1.wypłać(kwota);
Domyślna reakcja na wystąpienie
k2.wpłać(kwota);
wyjątku to zakończenie programu
}
(to lepsze niż działanie z błędem).
39/41
•
•
Pytanie: Jak lepiej reagować na wyjątek?
Odpowiedź: Łapać go.
class Konto {
// ...
public void wypłać(double kwota) { ... }
}
static void Main(string[] args) {
// wykonywanie przelewu
Konto k1 = new Konto(), k2 = new Konto();
// ...
double kwota = double.Parse(Console.ReadLine());
try
Blok objęty
{
kontrolą.
k1.wypłać(kwota);
k2.wpłać(kwota);
}
Blok łapania
catch
wyjątku.
{
Console.WriteLine("Operacja nieudana");
}
}
40/41
•
•
Pytanie: Jak lepiej reagować na wyjątek?
Odpowiedź: Łapać go.
class Konto {
// ...
public void wypłać(double kwota) { ... }
}
static void Main(string[] args) {
// wykonywanie przelewu
Konto k1 = new Konto(), k2 = new Konto();
// ...
double kwota = double.Parse(Console.ReadLine());
try
{
k1.wypłać(kwota);
W ten sposób możemy
k2.wpłać(kwota);
uzyskać informację o wyjątku,
}
który wystąpił.
catch(Exception ex)
{
Console.WriteLine("Operacja nieudana: "+ ex.Message);
}
}
41/41