Marek Tabędzki Programowanie obiektowe i zdarzeniowe 1/23 Programowanie obiektowe i zdarzeniowe wykład 6 polimorfizm Na poprzednim wykładzie: dziedziczenie jest sposobem na utworzenie nowej klasy na podstawie klasy już istniejącej, klasa pochodna dziedziczy pola i metody klasy bazowej, ale nie dziedziczy konstruktorów, klasa pochodna może dodawać własne pola i metody, a także przesłaniać pola i metody klasy bazowej, klasa pochodna może odwołać się do klasy bazowej przy pomocy słowa kluczowego base.
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 2/23 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 Pracownik jest Osobą. Co z tego wynika? Rzutowanie w górę: Tworzymy Pracownika, ale traktujemy go jako Osobę. Osoba os = new Pracownik("Piotr", "Nowak", 2200.0); os.wypłać();
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 3/23 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).
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 4/23 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
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 5/23 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) pr.wypłać(); Rzutowanie w dół: Jeszcze inne rozwiązanie null oznacza brak obiektu pod tą referencją
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 6/23 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() Console.WriteLine("rysuję figurę"); class Trójkąt : Figura public void rysuj() Console.WriteLine("rysuję trójkąt"); class Koło : Figura public void rysuj() Console.WriteLine("rysuję koło"); class Prostokąt : Figura public void rysuj() Console.WriteLine("rysuję prostokąt");
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 7/23 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();
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? class Figura rysuję figurę public void rysuj() Console.WriteLine("rysuję figurę"); Marek Tabędzki Programowanie obiektowe i zdarzeniowe 8/23
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 9/23 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
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 10/23 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;
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 11/23 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;
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 12/23 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.
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 13/23 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 Operację możemy wykonać dla 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.
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 14/23 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
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 15/23 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.
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 16/23 Interfejsy i klasy abstrakcyjne Niekiedy klasa bazowa istnieje tylko po to, aby stanowić uogólnienie klas z niej dziedziczących umożliwiać wspólne ich traktowanie (polimorfizm) Nigdy natomiast nie będziemy tworzyć obiektów tej klasy bazowej. Przykład: Figura (to nieokreślona figura). Taką klasę dobrze jest zdefiniować jako abstrakcyjną. abstract class Figura public void rysuj() Console.WriteLine("rysuję figurę"); Klasa abstrakcyjna może być definiowana tak, jak każda inna klasa (mieć pola, metody, konstruktory). Jedynie ograniczenie nie możemy tworzyć jej obiektów: Figura figura = new Figura(); Możemy jednak normalnie tworzyć referencje na klasę abstrakcyjną: Figura figura = new Prostokąt();
Niekiedy klasa abstrakcyjna ma metody, o których wiemy, że nigdy się nie wywołają (gdyż każda z klas pochodnych dostarczy swoją wersję tej metody). Możemy oznaczyć je jako abstrakcyjne. Metoda abstrakcyjna nie ma ciała a jedynie nagłówek. abstract class Figura public abstract void rysuj(); Klasa dziedzicząca z abstrakcyjnej musi dostarczyć implementację wszystkich metod abstrakcyjnych. class Prostokąt : Figura public override void rysuj() Console.WriteLine("rysuję prostokąt"); Po co nam metoda, która się nie wywoła? Jest konieczna, aby można było skorzystać z polimorfizmu stanowi ona wspólny dostęp do wszystkich wersji metody rysuj(). Figura figura = new Prostokąt(); figura.rysuj(); Marek Tabędzki Programowanie obiektowe i zdarzeniowe 17/23
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 18/23 Specyficzną odmianą klas abstrakcyjnych są interfejsy. Typ interfejsowy to zbiór nagłówków publicznych metod klasy. Interfejs nie może mieć: konstruktorów, zwykłych metod, pól. interface Figura void rysuj(); Dziedziczenie z typu interfejsowego wygląda tak samo, jak ze zwykłej klasy. class Prostokąt : Figura public void rysuj() Metody z interfejsu muszą być publiczne. Console.WriteLine("rysuję prostokąt"); Można implementować wiele interfejsów (dziedziczyć z wielu typów interfejsowych). Typu interfejsowe mogę nawzajem z siebie dziedziczyć.
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 19/23 Przykład: wyrażenie (a + b). class Liczba private int wart; public Liczba(int wart) this.wart = wart; public int Wartość() return wart; public override String ToString() return wart.tostring();
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 20/23 Przykład: wyrażenie (a + b). Klasa reprezentująca class Plus operator Plus przechowuje dwa private Liczba lewa, prawa; argumenty (Liczby). public Plus(Liczba l, Liczba p) lewa = l; prawa = p; public int Wartość() return lewa.wartość() + prawa.wartość(); public override String ToString() return String.Format("(0 + 1)", lewa, prawa);
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 21/23 Przykład: wyrażenie (a + b). static void Main(string[] args) Plus p = new Plus(new Liczba(2), new Liczba(3)); Console.WriteLine("0 = 1", p, p.wartość()); Console.ReadKey(); p Plus lew (2 + 3) = 5 pra Liczba 2 Liczba 3
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 22/23 A co z bardziej złożonym wyrażeniem? (a + (b * c)) static void Main(string[] args) Plus p = new Plus(new Liczba(1), new Razy(new Liczba(2), new Liczba(3))); Console.WriteLine("0 = 1", p, p.wartość()); Console.ReadKey(); p Plus lew pra (1 + (2 * 3)) = 7 Liczba 1 Razy lew pra Liczba Liczba 2 3
Marek Tabędzki Programowanie obiektowe i zdarzeniowe 23/23 A co z bardziej złożonym wyrażeniem? (a + (b * c)) interface Wyrażenie int Wartość(); class Liczba : Wyrażenie... Argumentem może być public int Wartość() return wart; Liczba lub... kolejny operator (np. Razy). class Plus : Wyrażenie W ogólnym przypadku: Wyrażenie. private Wyrażenie lewe, prawe; public Plus(Wyrażenie l, Wyrażenie p)... public int Wartość()... public override String ToString()... class Razy : Wyrażenie private Wyrażenie lewe, prawe; public Razy(Wyrażenie l, Wyrażenie p)... public int Wartość()... public override String ToString()...