1. Treści programowe:
Programowanie
obiektowe, klasy abstrakcyjne i anonimowe
– deklaracja i wykorzystanie
2. Cel zajęć:
Zrozumienie zasad
tworzenia i wykorzystania klas abstrakcyjnych oraz ich wykorzystania poprzez
dziedziczenie lub przez klasy anonimowe
3. Materiały
dydaktyczne:
· Klasy abstrakcyjne
Aby zadeklarować
klasę abstrakcyjną, użyj słowa kluczowego abstract przed słowem class. Możesz również zadeklarować w
niej metody abstrakcyjne, które nie posiadają ciała, oraz zwykłe metody z pełną
implementacją.
Klasy abstrakcyjne są
idealne, gdy chcesz zdefiniować wspólne cechy i zachowania dla grupy
powiązanych klas, ale szczegóły implementacji pozostawić podklasom. Stosuje się
je, gdy:
Cechy klas
abstrakcyjnych:
·
mogą
zawierać metody abstrakcyjne (metody nie posiadające implementacji),
·
mogą
zawierać zwykłe metody,
·
mogą
zawierać stałe statyczne,
·
klasa
abstrakcyjna może być rozszerzana przez inne klasy Java, zarówno zwykłe jak i
abstrakcyjne,
·
klasa
dziedzicząca klasę abstrakcyjną musi stworzyć implementację dla metod
abstrakcyjnych,
·
metody
abstrakcyjne nie mogą być statyczne, ponieważ nie posiadają implementacji,
·
w
klasach abstrakcyjnych możemy tworzyć konstruktory,
·
tworząc
instancję klasy abstrakcyjnej musimy stworzyć implementację dla metod
abstrakcyjnych.
Ideą metody
abstrakcyjnej jest to, że jest ona wymogiem dla każdej klasy, która
dziedziczy po klasie abstrakcyjnej. To klasa dziedzicząca jest zobowiązana
dostarczyć konkretną implementację. Dzięki temu
można wymusić pewne zachowania na podklasach, ale pozostawić im elastyczność w
ich realizacji.
-
Przykład tworzenia i wykorzystania klas abstrakcyjnych poprzez typowe
dziedziczenie
Klasa Figura to nasz szablon. Wymaga ona, by każda klasa
dziedzicząca miała metodę obliczPole(), ale nie
narzuca, jak to pole ma być liczone. Dzięki temu mamy pewność, że wszystkie
figury będą miały tę funkcjonalność.
Dodanie konstruktora
do klasy abstrakcyjnej pozwala na inicjalizowanie wspólnych pól we wszystkich
klasach dziedziczących. Zamiast powtarzać kod (this.typ
= "...") w każdym konstruktorze podklasy, możemy go umieścić w jednym
miejscu. Dzięki temu kod jest bardziej czytelny i łatwiejszy w utrzymaniu. Obowiązkowe
wywołanie super() zapewnia, że każde Kolo i
Kwadrat poprawnie zdefiniuje swój typ już w momencie tworzenia.
//
Klasa abstrakcyjna Figura
abstract class
Figura {
protected String typ;
// Konstruktor klasy abstrakcyjnej
public Figura(String typ) {
this.typ =
typ;
}
public abstract double
obliczPole(); // Abstrakcyjna metoda - musi
być nadpisana
public void pokazTypFigury() {
System.out.println("Jestem figurą typu: "
+ typ);
}
}
Klasy Kolo i Kwadrat dostarczają konkretne implementacje tej metody,
dostosowane do swoich potrzeb. Użycie adnotacji @Override podkreśla, że
nadpisujemy metodę z klasy nadrzędnej.
//
Klasa Koło dziedziczy po Figura
class Kolo extends Figura {
private double
promien;
public Kolo(double promien) {
// Wywołanie konstruktora klasy nadrzędnej
super("Koło");
this.promien = promien;
}
@Override
public double obliczPole() {
return Math.PI * promien * promien;
}
}
//
Klasa Kwadrat dziedziczy po Figura
class Kwadrat extends Figura {
private double
bok;
public Kwadrat(double bok) {
// Wywołanie konstruktora klasy nadrzędnej
super("Kwadrat");
this.bok =
bok;
}
@Override
public double obliczPole() {
return bok * bok;
}
}
W metodzie main, tworzymy obiekty Kolo
i Kwadrat, ale przypisujemy je do zmiennych typu Figura. Jest to możliwe,
ponieważ Kolo i Kwadrat są typem Figura.
Kiedy wywołujemy kolo.obliczPole(), Java wie, że ma użyć
metody z klasy Kolo, a kiedy wywołujemy kwadrat.obliczPole(), używa metody z klasy
Kwadrat.
To zachowanie
nazywamy polimorfizmem.
public
class KalkulatorFigur {
public static void
main(String[] args)
{
// Tworzenie obiektów podklas
Figura kolo = new Kolo(5.0);
Figura kwadrat = new Kwadrat(4.0);
// Wywołanie metody z ciałem, która została
odziedziczona
kolo.pokazTypFigury();
System.out.println("Pole: " + kolo.obliczPole());
System.out.println("------------------------------------");
// Wywołanie metody z ciałem i metody abstrakcyjnej
kwadrat.pokazTypFigury();
System.out.println("Pole: " + kwadrat.obliczPole());
}
}
-
Przykład tworzenia i wykorzystania klas abstrakcyjnych poprzez stworzenie klas
anonimowych
W przykładzie
poniżej, klasy anonimowe Kolo
i Kwadrat są jednorazowe.
Zamiast tworzyć oddzielne pliki (Kolo.java, Kwadrat.java), definiujemy ich
zachowanie w miejscu, w którym tworzymy obiekt. Dzięki temu oszczędzamy kod i
uzyskujemy elastyczność.
Klasy anonimowe są
szczególnie przydatne, gdy potrzebujesz prostej implementacji dla interfejsu
lub abstrakcyjnej klasy, która będzie użyta tylko w jednym miejscu.
public
class KalkulatorFigur {
public static void
main(String[] args)
{
// Tworzenie obiektu Koło jako klasy anonimowej.
// Wywołanie konstruktora klasy
nadrzędnej przez super("Koło").
Figura kolo = new Figura("Koło") {
private double
promien = 5.0;
@Override
public double
obliczPole() {
return Math.PI * promien * promien;
}
};
// Tworzenie obiektu Kwadrat jako klasy anonimowej.
// Wywołanie konstruktora klasy
nadrzędnej przez super("Kwadrat").
Figura kwadrat = new Figura("Kwadrat") {
private double
bok = 4.0;
@Override
public double
obliczPole() {
return bok * bok;
}
};
// Wyświetlanie danych dla Koła.
kolo.pokazTypFigury();
System.out.println("Pole: " + kolo.obliczPole());
System.out.println("------------------------------------");
// Wyświetlanie danych dla Kwadratu.
kwadrat.pokazTypFigury();
System.out.println("Pole: " + kwadrat.obliczPole());
}
}
4. Zadania
Zadanie 1. System zarządzania kontami bankowymi
Zadanie: Stwórz abstrakcyjną klasę KontoBankowe z polami numerKonta
i saldo. Dodaj do niej abstrakcyjną metodę wyplata(double
kwota) i zwykłą metodę wplata(double kwota).
Następnie zaimplementuj dwie klasy podrzędne: KontoOsobiste
i KontoFirmowe. W klasie KontoOsobiste
metoda wyplata powinna odejmować kwotę, a w klasie KontoFirmowe
powinna dodawać opłatę transakcyjną, np. 0.5% od wypłacanej kwoty. Wykorzystaj
mechanizm dziedziczenia.
Zadanie 2. Hierarchia oprogramowania
Zaprojektuj
abstrakcyjną klasę Oprogramowanie z metodą uruchom() i polem wersja.
Dodaj abstrakcyjną metodę pobierzWersje(). Następnie
utwórz klasy SystemOperacyjny i Aplikacja,
które dziedziczą po Oprogramowanie. W SystemOperacyjny
zaimplementuj pobierzWersje tak, aby zwracała wersję
w formacie 10.1, a w Aplikacja – w formacie 2.0 beta. Wykorzystaj mechanizm
dziedziczenia. Klasy SystemOperacyjny i
Aplikacja stwórz jako klasy anonimowe.
Zadanie 3. Wypożyczalnia sprzętu
sportowego
Stwórz klasę
abstrakcyjną SprzetSportowy z polami nazwa i
cena oraz abstrakcyjną metodą obliczKosztWynajmu(int dni). Następnie stwórz dwie klasy: Narty i Rower.
Klasa Narty powinna liczyć koszt wynajmu, dodając stałą opłatę za przygotowanie
sprzętu, np. 20 zł, a klasa Rower – opłatę za kask w wysokości 5 zł za dzień.
Wykorzystaj mechanizm dziedziczenia.
Zadanie 4. Kreator postaci do gry RPG
Utwórz abstrakcyjną
klasę Postac z polami sila,
zrecznosc i abstrakcyjną metodą atakuj(). Następnie
stwórz dwie klasy dziedziczące: Rycerz i Lucznik.
Klasa Rycerz powinna implementować metodę atakuj, zadając obrażenia równe sila * 1.5, a Lucznik – zrecznosc * 2.0. W main stwórz
tablicę postaci i dla każdej z nich wywołaj metodę atakuj(). Wykorzystaj
mechanizm dziedziczenia.
Zadanie 5. System powiadomień
Stwórz abstrakcyjną
klasę Powiadomienie z abstrakcyjną metodą wyslijPowiadomienie(String
wiadomosc). Następnie utwórz dwie klasy podrzędne: PowiadomienieEmail i PowiadomienieSMS.
W klasie PowiadomienieEmail metoda wyslijPowiadomienie powinna dodawać do wiadomości temat
wiadomości, a w klasie PowiadomienieSMS – limit
znaków, jeśli wiadomość jest za długa, np. 160 znaków. Klasy PowiadomienieEmail i PowiadomienieSMS
utwórz jako klasy anonimowe.
Zadanie 6. Własny pomysł
Napis program według
własnego pomysłu, w którym pokaż wykorzystanie klasy abstrakcyjnej.