koncepcja OOP dla początkujących: czym jest dziedziczenie?
Dziedziczenie jest jednym z podstawowych pojęć języków programowania zorientowanego obiektowo (OOP). Jest to mechanizm, w którym można uzyskać klasę z innej klasy dla hierarchii klas, które mają zestaw atrybutów i metod.
można go używać do deklarowania różnego rodzaju WYJĄTKÓW, dodawania niestandardowej logiki do istniejących struktur, a nawet mapowania modelu domeny do bazy danych.,
Zadeklaruj hierarchię dziedziczenia
w Javie każda klasa może być pochodna tylko z jednej innej klasy. Klasa ta nazywana jest klasą nadrzędną lub klasą nadrzędną. Klasa pochodna nazywana jest podklasą lub klasą potomną.
używasz słowa kluczowego extends, aby zidentyfikować klasę, którą rozszerza twoja podklasa. Jeśli nie zadeklarujesz klasy nadrzędnej, twoja klasa domyślnie rozszerza obiekt klasy. Object jest korzeniem wszystkich hierarchii dziedziczenia; jest to jedyna klasa w Javie, która nie rozszerza innej klasy.
poniższy schemat i fragmenty kodu przedstawiają przykład prostej hierarchii dziedziczenia.,
Klasa BasicCoffeeMachine nie deklaruje klasy nadrzędnej i domyślnie rozszerza obiekt klasy. Możesz sklonować przykładowy projekt CoffeeMachine na Githubie.
Klasa PremiumCoffeeMachine jest podklasą klasy BasicCoffeeMachine.
modyfikatory dziedziczenia i dostępu
modyfikatory dostępu definiują, które klasy mogą uzyskać dostęp do atrybutu lub metody. W jednym z moich poprzednich postów na temat enkapsulacji pokazałem ci, jak możesz ich użyć do implementacji mechanizmu ukrywania informacji. Ale to nie jedyny przypadek, w którym musisz znać różne modyfikatory., Wpływają one również na elementy i atrybuty, do których można uzyskać dostęp w hierarchii dziedziczenia.
oto krótki przegląd różnych modyfikatorów:
- prywatne atrybuty lub metody mogą być dostępne tylko w ramach tej samej klasy.
- atrybuty i metody bez modyfikatora dostępu mogą być dostępne w ramach tej samej klasy i wszystkich innych klas w ramach tego samego pakietu.
- Protected attributes or methods can be accessed within the same class, by all classes within the same package, and by all subclasses.,
- publiczne atrybuty i metody mogą być dostępne dla wszystkich klas.
jak widać na tej liście, podklasa może uzyskać dostęp do wszystkich chronionych i publicznych atrybutów i metod klasy nadrzędnej. Jeśli podklasa i klasa nadrzędna należą do tego samego pakietu, podklasa może również uzyskać dostęp do wszystkich prywatnych atrybutów i metod klasy nadrzędnej.
robię to dwa razy w konstruktorze klasy PremiumCoffeeMachine.
najpierw używam słowa kluczowego super do wywołania konstruktora superklasy. Konstruktor jest publiczny, a podklasa ma do niego dostęp., Słowo kluczowe super odwołuje się do superklasa. Możesz go użyć, aby uzyskać dostęp do atrybutu lub wywołać metodę klasy nadrzędnej, która zostanie nadpisana przez bieżącą podklasę. Ale więcej o tym w dalszej części.
chroniony atrybut configMap jest definiowany przez klasę BasicCoffeeMachine. Rozszerzając tę klasę, atrybut staje się również częścią klasy PremiumCoffeeMachine i mogę dodać konfigurację wymaganą do parzenia espresso na mapie.,
nadpisywanie metod
dziedziczenie nie tylko dodaje wszystkie publiczne i chronione metody klasy nadrzędnej do podklasy, ale także pozwala na zastąpienie ich implementacji. Metoda podklasy nadpisuje następnie metodę klasy nadrzędnej. Mechanizm ten nazywany jest polimorfizmem.
używam tego w klasie PremiumCoffeeMachine, aby rozszerzyć możliwości parzenia kawy z ekspresu. Metoda brewCoffee metody BasicCoffeeMachine może tylko parzyć kawę filtrującą.
nadpisuję tę metodę w klasie PremiumCoffeeMachine, aby dodać obsługę CoffeeSelection.,ESPRESSO. Jak widać w fragmencie kodu, słowo kluczowe super jest bardzo pomocne, jeśli nadpisujesz metodę. Metoda brewCoffee w podstawowej maszynie coffeemachine obsługuje już selekcję kawy.FILTER_COFFEE i rzuca CoffeeException dla nieobsługiwanych CoffeeSelections.
mogę to ponownie wykorzystać w mojej nowej metodzie brewCoffee. Zamiast powtarzać tę samą logikę, sprawdzam tylko czy kawa to ESPRESSO. Jeśli tak nie jest, używam super słowa kluczowego, aby wywołać metodę brewCoffee superclass.,
zapobiega nadpisywaniu metody
Jeśli chcesz mieć pewność, że żadna podklasa nie może zmienić implementacji metody, możesz zadeklarować ją jako ostateczną. W przykładzie tego postu zrobiłem to dla metody addBeans klasy BasicCoffeeMachine.
często dobrym pomysłem jest, aby wszystkie metody były ostateczne, które są wywoływane przez konstruktor. Uniemożliwia to dowolnej podklasie, często nieumyślnie, zmianę zachowania konstruktora.,
podklasa jest również typu superclass
podklasa nie tylko dziedziczy atrybuty i metody superclass, ale także dziedziczy typy superclass. W przykładzie basiccoffeemachine jest typu BasicCoffeeMachine and Object. Obiekt PremiumCoffeeMachine jest typu PremiumCoffeeMachine, BasicCoffeeMachine i Object.
z tego powodu możesz rzucić obiekt PremiumCoffeeMachine do typu BasicCoffeeMachine.
BasicCoffeeMachinee coffeeMachine = (BasicCoffeeMachine) PremiumCoffeeMachine(beans);
który umożliwia pisanie kodu, który używa superklasy i wykonuje go ze wszystkimi podklasami.,
w tym przykładzie zwraca się kod metody createCoffeeMachine, a metoda makeCoffee używa klasy BasicCoffeeMachine. Ale metoda createCoffeeMachine tworzy instancję nowego obiektu PremiumCoffeeMachine. Gdy zostanie zwrócony przez metodę, obiekt zostanie automatycznie oddany do BasicCoffeeMachine i kod może wywołać wszystkie publiczne metody klasy BasicCoffeeMachine.
obiekt coffeeMachine zostaje oddany do BasicCoffeeMachine, ale nadal jest to PremiumCoffeeMachine., Kiedy więc metoda makeCoffee wywoła metodę brewCoffee, wywoła nadpisaną metodę w klasie PremiumCoffeeMachine.
Definiowanie klas abstrakcyjnych
klasy abstrakcyjne różnią się od innych klas, o których rozmawialiśmy. Można je rozszerzyć, ale nie utworzyć instancji. Dzięki temu są idealne do reprezentowania uogólnień pojęciowych, które nie istnieją w danej domenie, ale umożliwiają ponowne użycie części kodu.
używasz słowa kluczowego abstract, aby zadeklarować klasę lub metodę jako abstrakcyjną. Klasa abstrakcyjna nie musi zawierać żadnych abstrakcyjnych metod., Ale metoda abstrakcyjna musi być zadeklarowana przez klasę abstrakcyjną.
zreformujmy przykład ekspresu do kawy i przedstawmy klasę AbstractCoffeeMachine jako klasę nadrzędną klasy BasicCoffeeMachine. Deklaruję tę klasę jako abstrakcyjną i definiuję metodę abstract brewCoffee.
public abstract class AbstractCoffeeMachine { protected Map<CoffeeSelection, Configuration> configMap; public AbstractCoffeeMachine() { this.configMap = new HashMap<CoffeeSelection, Configuration>(); } public abstract Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException; }
jak widzisz, nie podaję ciała abstrakcyjnej metody brewCoffee. Po prostu deklaruję to tak, jak zrobiłbym to w interfejsie., Gdy rozszerzysz klasę AbstractCoffeeMachine, będziesz musiał zdefiniować podklasę jako abstract lub nadpisać metodę brewCoffee, aby zaimplementować ciało metody.
dokonuję kilku drobnych zmian w klasie BasicCoffeeMachine. Teraz rozszerza klasę AbstractCoffeeMachine, a już istniejąca metoda brewCoffee nadpisuje metodę abstrakcyjną klasy superclass.
kolejną rzeczą, którą zmieniłem, jest konstruktor klasy BasicCoffeeMachine. Teraz wywołuje konstruktor superclass i dodaje parę klucz-wartość do atrybutu configMap bez tworzenia instancji Mapy., Jest definiowana i tworzona przez abstrakcyjną superklasę i może być używana we wszystkich podklasach.
jest to jedna z głównych różnic między abstrakcyjną klasą nadrzędną a interfejsem. Klasa abstrakcyjna nie tylko pozwala deklarować metody, ale także definiować atrybuty, które nie są statyczne i ostateczne.
podsumowanie
jak już zauważyłeś, dziedziczenie jest potężną koncepcją, która pozwala Ci zaimplementować podklasę, która rozszerza superklasę. W ten sposób podklasa dziedziczy wszystkie chronione i publiczne atrybuty i metody oraz typy superklasy., Następnie możesz użyć odziedziczonych atrybutów klasy nadrzędnej, użyć lub nadpisać odziedziczone metody i przenieść podklasę do dowolnego typu jej klasy nadrzędnej.
możesz użyć klasy abstrakcyjnej, aby zdefiniować ogólną abstrakcję, której nie można utworzyć instancji. W ramach tej klasy możesz zadeklarować metody abstrakcyjne, które muszą być nadpisane przez nieabstraktowe podklasy. Jest to często używane, jeśli implementacja tej metody jest specyficzna dla każdej podklasy, ale chcesz zdefiniować ogólne API dla wszystkich klas hierarchii.