Ponowne wykorzystanie klas c Krzysztof Barteczko 2014 Rodzaje
Ponowne wykorzystanie klas (c) Krzysztof Barteczko 2014
Rodzaje ponownego wykorzystania (c) Krzysztof Barteczko 2014
Dziedziczenie - koncepcja Dziedziczenie polega na przejęciu właściwości i funkcjonalności obiektów innej klasy i ewentualnej ich modyfikacji i/lub uzupelnieniu w taki sposób, by były one bardziej wyspecjalizowane. Jest to relacja, nazywana generalizacją-specjalizacją: B "jest typu" A, "B jest A", a jednocześnie B specjalizuje A. A jest generalizacją B. (c) Krzysztof Barteczko 2014
Zacznijmy dziedziczyć Niech klasa Publication, opisuje właściwości publikacji, które kupuje i sprzedaje księgarnia: public class Publication { private String title; private String publisher; private int year; private String ident; private double price; private int quantity; public Publication() { } public Publication(String t, String pb, int y, tring i, double pr, int q) { title = t; publisher = pb; year = y; ident = i; price = pr; quantity = q; } public String get. Title() { return title; } public String get. Publisher() { return publisher; } public int get. Year() { return year; } public String get. Ident() { return ident; } public double get. Price() { return price; } public void set. Price(double p) { price = p; } public int get. Quantity() { return quantity; } public void buy(int n) { quantity += n; } public void sell(int n) { quantity -= n; } } (c) Krzysztof Barteczko 2014 Za pomocą tej klasy nie możemy w pełni opisać książek. Książki są szczególną, "wyspecjalizowaną" wersją publikacji. Więc powinniśmy stworzyć nową klasę opisującą książki, o nazwie np. Book. Moglibyśmy to robić od podstaw Ale po co?
Składnia dla dziedziczenia (c) Krzysztof Barteczko 2014
Klasa Book public class Book extends Publication { private String author; public Book(String aut, String tit, String pub, int y, String id, double price, int quant) { Użycie w konstruktorze następującej super(tit, pub, y, id, price, quant); konstrukcji składniowej: author = aut; } super(lista_argumentów); public Book() { } public String get. Author() { return author; } oznacza wywołanie konstruktora klasy bazowej z argumentami lista_argumentów. Jeśli występuje - MUSI być pierwszą instrukcją konstruktora klasy pochodnej. Jeśli nie występuje - przed utworzeniem obiektu klasy pochodnej zostanie wywołany konstruktor bezparametrowy klasy bazowej. } (c) Krzysztof Barteczko 2014
Tytuł Przy tak zdefiniowanej klasie Book możemy utworzyć jej obiekt: Book b = new Book("James Gossling", "Moja Java", "WNT", 2002, "ISBN 6893", 51. 0, 0); (c) Krzysztof Barteczko 2014
Jeśli B extends A, to B jest A (c) Krzysztof Barteczko 2014
Rozszerzające konwersje refrencyjne (c) Krzysztof Barteczko 2014
Znaczenie konwersji rozszerzających (c) Krzysztof Barteczko 2014
Problem 1 (c) Krzysztof Barteczko 2014
Referencyjne konwersje zawężające (c) Krzysztof Barteczko 2014
Problem 2 Co by się jednak stało, gdyby tej metodzie przekazać obiekt klasy Journal? Kompilator nie będzie protestował, bo Journal pochodzi od Publication. W fazie kompilacji nie jest też możiwe sprawdzenie co tak naprawdę siedzi w zmiennej p 1, czy to jest referencja do obiektu klasy Book czy też może jakiejś innej klasy pochodnej od Publication. Prawda wyjdzie na jaw dopiero w fazie wykonania programu: rzutowanie do typu Book spowoduje błąd o nazwie Class. Cast. Exception i przerwanie programu, ponieważ w p 1 znajduje się referencja do obiektu klasy Journal i nie da się jej rzutować na referencję do obiektu typu Book. (c) Krzysztof Barteczko 2014
Stwierdzanie typu - operator instanceof (c) Krzysztof Barteczko 2014
Użycie instanceof (c) Krzysztof Barteczko 2014
Stwierdzanie typu – get. Class() Metoda get. Class() zwraca faktyczny typ obiektu w postaci referencji do obiektu klasy Class. Obiekty tej klasy oznaczają klasy. W kontekście: Book b = new Book(. . . ); Publication p = b; Class c = p. get. Class(); zmienna c będzie oznaczać klasę Book. Łatwo możemy się dowiedzieć o nazwę klasy: Class klasa = p. get. Class(); String nazwa = klasa. get. Name(); Przy takim sprawdzaniu można też użyć tzw. literałów klasowych. Mają one postać: nazwa_klasy. class i w ten sposób sprawdzić o jaką klasę chodzi. (c) Krzysztof Barteczko 2014
instanceof a get. Class - różnica Załóżmy, że mamy jeszcze dodatkowo klasę Tabloid, dziedzicząca klasę Journal i obiekt tej klasy: Tabloid t = new Tabloid(. . . ); Wtedy: t instanceof Journal zwróci true, bo brane są pod uwagę wszystkie podtypy Journal, a t jest typu Tabloid czyli podtypu Journal, natomiast t. get. Class() == Journal. class będzie miało wartość false, bo sprawdzana jest konkretna klasa, którą w tym przypadku jest Tabloid. Oczywiście, t. get. Class(). get. Name() zwróci napis "Tabloid". (c) Krzysztof Barteczko 2014
Cechy dziedziczenia w Javie W Javie każda klasa może bezpośrednio odziedziczyć tylko jedną klasę. Ale pośrednio może mieć dowolnie wiele nadklas, co wynika z hierarchii dziedziczenia. Ta hierarchia zawsze zaczyna się na klasie Object (której definicja znajduje się w zestawie stanardowych klas Javy). Zatem w Javie wszystkie klasy pochodzą pośrednio od klasy Object. Jeśli definiując klasę nie użyjemy słowa extends (nie zażądamy jawnie dziedziczenia), to i tak nasza klasa domyślnie będziedziczyć klasę Object (tak jakbyśmy napisali class A extends Object). Z tego wynika, że: referencję do obiektu dowolnej klasy można przypisać zmiennej typu Object (c) Krzysztof Barteczko 2014
Przedefiniowanie metod (c) Krzysztof Barteczko 2014
Klasa Car class Car extends Vehicle { private String nr. Rej; private int tank. Capacity; private int fuel; public Car(String nr, Person owner, int w, int h, int l, int weight, int tank. Cap) { super(owner, w, h, l, weight); nr. Rej = nr; tank. Capacity = tank. Cap; } public void fill(int amount) { fuel += amount; if (fuel > tank. Capacity) fuel = tank. Capacity; } public void start() { if (fuel > 0) super. start(); else error("Brak paliwa"); } public String to. String() { return "Samochód nr rej " + nr. Rej + " - " + get. State(); } (c) Krzysztof Barteczko 2014
Przedefiniowanie metod - definicja (c) Krzysztof Barteczko 2014
Wywoływanie przedefiniowanych metod z nadklasy (c) Krzysztof Barteczko 2014
Użycie Wobec obiektów klasy Car możemy używać: wszystkich nieprywatnych (i nieprzedefiniowanych) metod klasy Vehicle (np. crash(. . . )), przedefiniowanych w klasie Car metod klasy Vehicle, własnych metod klasy Car c = new Car("WA 1090", new Person("Janek", "0909"), 100, 50), d = new Car("WB 2010", new Person("Ania", "1010"), 100, 50); try { c. start(); } catch (Exception exc) { Samochód nr rej WA 1090 - STOPPED - Brak paliwa System. out. println(c + " - " + exc. get. Message()); } Samochód nr rej WA 1090 - STOPPED c. fill(10); Samochód nr rej WA 1090 - MOVING System. out. println(c); Samochód nr rej WA 1090 - STOPPED c. start(); Samochód nr rej WB 2010 - STOPPED System. out. println(c); Samochód nr rej WB 2010 - MOVING c. stop(); System. out. println(c); Samochód nr rej WA 1090 - MOVING d. fill(20); Samochód nr rej WA 1090 - BROKEN System. out. println(d); Samochód nr rej WB 2010 - BROKEN d. start(); Samochód nr rej WA 1090 - STOPPED System. out. println(d); Samochód nr rej WB 2010 - STOPPED c. start(); System. out. println(c); Samochód nr rej WA 1090 - MOVING c. crash(d); Samochód nr rej WB 2010 - MOVING System. out. println(c + "n" + d); c. repair(); d. repair(); System. out. println(c + "n" + d); c. start(); d. start(); System. out. println(c + "n" + d); (c) Krzysztof Barteczko 2014
Zwracajmy this (c) Krzysztof Barteczko 2014
Kowariantne typy wyników W sytuacji: class A { S met() {. . . } } class B extends A { T met() {. . . } } metoda met ma kowariantny typ wyniku, jeśli T jest podtypem S. Kowariancja znaczy "współzmienność" - typ wyniku metody zmienia się w tym samym kierunku hierarchii dziedziczenia, jak kierunek dziedziczenia klas, w których jest definiowana. Typy wyników metod przedefiniowanych mogą być kowariantne. (c) Krzysztof Barteczko 2014
Użycie kowariantnych typów wyników (c) Krzysztof Barteczko 2014
Przedefiniowanie metod w wyliczeniach public enum Veh. State { BROKEN("ZEPSUTY"), STOPPED("STOI"), MOVING("JEDZIE"); private String opis; Veh. State(String s) { opis = s; } public String to. String() { return opis; } } amochód nr rej WA 1090 - ZEPSUTY Samochód nr rej WB 2010 - ZEPSUTY Samochód nr rej WA 1090 - JEDZIE Samochód nr rej WB 2010 - JEDZIE (c) Krzysztof Barteczko 2014
Przedefiniowując metody używajmy @Override (c) Krzysztof Barteczko 2014
Jak są wywoływane przedefiniowane metody? (c) Krzysztof Barteczko 2014
Metody wirtualne Decyduje faktyczny typ: Jak to możliwe? Kompilator nie zna faktycznego typu: public static void main(String args[]) { Car c = new Car(. . . ); Rydwan r = new Rydwan(. . . ); Vehicle v; if (args[0]. equals("Rydwan")) v = r; else v = c; v. start(); } Metoda start() z klasy Vehicle jest metodą wirtualną, a dla takich metod wiązanie odwołań z kodem następuje w fazie wykonania, a nie w fazie kompilacji. Nazywa się to "dynamic binding" lub "late binding". (c) Krzysztof Barteczko 2014
Polimorfizm (c) Krzysztof Barteczko 2014
Przykład polimorfizmu – hierarchia klas zwierząt class Zwierz { String name = "bez imienia"; Zwierz() { } Zwierz(String s) { name = s; } String get. Typ() { return "Jakis zwierz"; } String get. Name() { return name; } String get. Voice() { return "? "; } // Metoda speak symuluje wydanie głosu poprzez wypisanie odpowiedniego komunikatu void speak() { System. out. println(get. Typ()+" "+get. Name()+" mówi "+get. Voice()); } } class Pies extends Zwierz { Pies() { } Pies(String s) { super(s); } String get. Typ() { return "Pies"; } String get. Voice() { return "HAU, HAU!"; } } class Kot extends Zwierz { Kot() { } Kot(String s) { super(s); } String get. Typ() { return "Kot"; } String get. Voice() { return "Miauuuu. . . "; } (c) Krzysztof Barteczko 2014 }
Przykład polimorfizmu – rozmowy zwierząt static void animal. Dialog(Zwierz z 1, Zwierz z 2) { z 1. speak(); z 2. speak(); System. out. println("--------------------"); } //. . . Zwierz z 1 = new Zwierz(), z 2 = new Zwierz(); Pies pies = new Pies(), Jakis zwierz bez imienia mówi ? kuba = new Pies("Kuba"), Jakis zwierz bez imienia mówi ? -------------------- reksio = new Pies("Reksio"); Pies Kuba mówi HAU, HAU! Kot kot = new Kot(); Pies Reksio mówi HAU, HAU! animal. Dialog(z 1, z 2); animal. Dialog(kuba, reksio); animal. Dialog(kuba, kot); animal. Dialog(reksio, pies); --------------------Pies Kuba mówi HAU, HAU! Kot bez imienia mówi Miauuuu. . . --------------------Pies Reksio mówi HAU, HAU! Pies bez imienia mówi HAU, HAU! -------------------- (c) Krzysztof Barteczko 2014
Znaczenie polimorfizmu Dzięki wirtualności metod get. Typ() i get. Voice() metoda speak(), określona w klasie Zwierz prawidłowo działa dla różnych zwierząt (obiektów podklasy Zwierz). Jedna definicja metody speak() załatwiła nam wszystkie potrzeby (dotyczące dialogów różnych zwerząt). Co więcej – będzie ona tak samo użyteczna dla każdej nowej podklasy Zwierza, którą kiedykolwiek w przyszłości wprowadzimy. (c) Krzysztof Barteczko 2014
Polimorfizm na codzień (c) Krzysztof Barteczko 2014
Kompozycja Z koncepcyjnego punktu widzenia kompozycja oznacza, że "obiekt jest zawarty w innym obiekcie”. Jest to relacja "całość – część" ( B "zawiera" A). Np. obiekty typu Biurko zawierają obiekty typu Szuflada. Robimy to bez przerwy, np. stosując w klasach pola typu String. (c) Krzysztof Barteczko 2014
Delegowanie odwołań class Text { //. . . String cont; //. . . String get. Cont() { return cont; } } Text txt = new Text(. . . ); W różnych środowiskach uruchomieniowych delegowanie odwołań przy kompozycji jest łatwe, bowiem zapewniona jest automatyczna generacja kodu na podstawie naszych wyborów z dialogów. DEMO. aby uzyskać długość tekstu musimy napisać: txt. get. Cont(). length(); "Skrót", który można dostarczyć polega na zdefiniowaniu w klasie Text metody length(): public int length() { return cont. length(); } Taki rodzaj definicji nazywa się delegowaniem wywołań metod. Teraz możemy pisać prościej: txt. length(); (c) Krzysztof Barteczko 2014
Kompozycja a dziedziczenie Kompozycja ma czasem przewagę nad dziedziczeniem, ponieważ dziedziczenie może prowadzić do tzw. „słabej hermetyzacji kodu klasy bazowej” (demo z treści wykladu). REGUŁA Należy dostosowywać sposób ponownego wykorzystania do dziedziny problemu. Dziedziczenia używamy tylko wtedy, kiedy spełniona jest relacja "B jest A”. Czasem okazuje się, że taka relacja jest pozorna lub może ulegać zmianom w cyklu życiowym obiektów. Dobrym testem jest postawienie pytania czy zawsze, w każdych okolicznościach działania naszego programu można sensownie myśleć o rozszerzającej konwersji referencyjnej typu podklasy do typu nadklasy. (c) Krzysztof Barteczko 2014
- Slides: 38