Dziedziczenie i polimorfizm
Poznaj zasady programowania obiektowego, takie jak dziedziczenie, klasy abstrakcyjne i polimorfizm w Scali.
Dziedziczenie i polimorfizm to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 2 z 2. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Scala for Backend Engineering & Functional Programming, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Scala for Backend Engineering & Functional Programming zawiera 2 lekcji w sumie.
Czym jest dziedziczenie?
Dziedziczenie to podstawowe pojęcie programowania obiektowego (OOP), które pozwala nowej klasie dziedziczyć właściwości i zachowania (metody) z istniejącej klasy.
Można je traktować jako relację „rodzic–dziecko”. Klasa „dziecko” (podklasa) ponownie wykorzystuje kod klasy „rodzic” (nadklasy), co oszczędza czas i zapewnia spójność.
- Umożliwia ponowne wykorzystanie kodu.
- Ustanawia relację „jest” (np. obiekt Dog jest obiektem Animal).
- Pomaga organizować kod w hierarchię.
Pierwsza klasa bazowa
Zdefiniujmy prostą klasę Animal. Będzie to nasza klasa bazowa, nazywana także nadklasą lub klasą rodzica. Ma ona podstawowe zachowanie: wydawanie dźwięku.
Uruchom kod, aby zobaczyć naszego przykładowego zwierzaka w działaniu!
class Animal {
val name: String = "Generic Animal"
def speak(): Unit = {
println(s"$name makes a sound.")
}
}
object Main {
def main(args: Array[String]): Unit = {
val myAnimal = new Animal()
myAnimal.speak()
}
}Rozszerzanie za pomocą `extends`
Utwórzmy teraz bardziej konkretne zwierzę — Dog. Dog jest obiektem Animal, dlatego może dziedziczyć po klasie Animal za pomocą słowa kluczowego extends.
Możemy także przesłonić metodę speak, aby nadać klasie Dog jej własny, charakterystyczny dźwięk. Proszę zwrócić uwagę na słowo kluczowe override.
class Animal {
val name: String = "Generic Animal"
def speak(): Unit = {
println(s"$name makes a sound.")
}
}
class Dog extends Animal {
override val name: String = "Dog"
override def speak(): Unit = {
println(s"$name barks!")
}
}
object Main {
def main(args: Array[String]): Unit = {
val myAnimal = new Animal()
val myDog = new Dog()
myAnimal.speak() // Generic Animal makes a sound.
myDog.speak() // Dog barks!
}
}Moc `override`
Słowo kluczowe override ma w Scali kluczowe znaczenie z kilku powodów:
- Jasność: Wyraźnie informuje innych programistów, że ta metoda lub pole zastępuje składową nadklasy.
- Bezpieczeństwo: Kompilator sprawdza, czy rzeczywiście przesłaniają Państwo istniejącą składową. Jeśli wystąpi literówka lub zmieni się metoda nadklasy, kompilator powiadomi o problemie.
- Intencja: Jednoznacznie wskazuje zamiar wyspecjalizowania lub zmiany odziedziczonego zachowania.
Podczas redefiniowania odziedziczonych składowych zawsze należy używać override!
Polimorfizm: wiele postaci
Polimorfizm oznacza „wiele postaci”. W programowaniu obiektowym pozwala traktować obiekty różnych klas jak obiekty wspólnej nadklasy.
Oznacza to, że można mieć zmienną typu Animal, która przechowuje obiekt Dog, obiekt Cat lub dowolną inną podklasę Animal. Po wywołaniu metody na tej zmiennej zostanie wykonana właściwa metoda odpowiadająca rzeczywistemu typowi obiektu.
- Umożliwia tworzenie elastycznego i rozszerzalnego kodu.
- Pozwala używać jednego interfejsu dla różnych typów danych.
- Jest realizowany za pomocą przesłaniania metod.
Polimorfizm w praktyce
Zobaczmy polimorfizm w działaniu. Utworzymy klasę Cat podobną do Dog. Następnie umieścimy różne typy zwierząt na liście ich wspólnego nadtypu, Animal, i wywołamy ich metodę speak.
Proszę zauważyć, że każde zwierzę wydaje własny, charakterystyczny dźwięk, mimo że wszystkie są traktowane na liście jako typ Animal.
class Animal {
val name: String = "Generic Animal"
def speak(): Unit = {
println(s"$name makes a sound.")
}
}
class Dog extends Animal {
override val name: String = "Dog"
override def speak(): Unit = {
println(s"$name barks!")
}
}
class Cat extends Animal {
override val name: String = "Cat"
override def speak(): Unit = {
println(s"$name meows!")
}
}
object Main {
def main(args: Array[String]): Unit = {
val animals: List[Animal] = List(
new Animal(),
new Dog(),
new Cat()
)
animals.foreach(_.speak())
}
}Wprowadzenie do klas abstrakcyjnych
Czasami potrzebna jest klasa bazowa, która definiuje wspólny interfejs, ale nie zapewnia kompletnej implementacji niektórych składowych. Właśnie do tego służą klasy abstrakcyjne.
Klasa abstrakcyjna:
- Nie może być bezpośrednio instancjonowana (nie można utworzyć obiektu klasy abstrakcyjnej).
- Może zawierać metody abstrakcyjne (metody bez implementacji) oraz metody konkretne (metody z implementacją).
- Jest szablonem, który jej podklasy muszą uzupełnić.
Tworzenie abstrakcyjnych szablonów
Zdefiniujmy abstract class Shape. Każdy kształt ma pole powierzchni, ale wzór służący do jego obliczenia różni się w zależności od konkretnego kształtu (koła, prostokąta itd.).
Dlatego deklarujemy area jako metodę abstrakcyjną. Proszę zauważyć, że nie ma ona ciała (brak = { ... }). Podklasy będą odpowiedzialne za zapewnienie jej implementacji.
abstract class Shape {
def area: Double // Abstract method, no implementation
def describe(): Unit = {
println("This is a geometric shape.")
}
}
// You cannot do: new Shape() - it's abstract!
object Main {
def main(args: Array[String]): Unit = {
println("Abstract class Shape defined.")
println("Cannot instantiate Shape directly.")
}
}Implementacje konkretne
Utwórzmy teraz konkretne podklasy, takie jak Circle i Rectangle, które rozszerzają naszą abstract class Shape. Te podklasy muszą zapewnić implementację abstrakcyjnej metody area.
Dziedziczą one także konkretną metodę describe() z klasy Shape.
abstract class Shape {
def area: Double
def describe(): Unit = {
println("This is a geometric shape.")
}
}
class Circle(radius: Double) extends Shape {
override def area: Double = math.Pi * radius * radius
}
class Rectangle(width: Double, height: Double) extends Shape {
override def area: Double = width * height
}
object Main {
def main(args: Array[String]): Unit = {
val circle = new Circle(5)
val rectangle = new Rectangle(4, 6)
println(s"Circle area: ${circle.area}")
circle.describe()
println(s"Rectangle area: ${rectangle.area}")
rectangle.describe()
}
}Sprawdzian z dziedziczenia i polimorfizmu
Które z poniższych stwierdzeń dotyczących dziedziczenia i polimorfizmu w Scali są PRAWDZIWE?
Podsumowanie: dziedziczenie i polimorfizm
Świetna praca! W tej lekcji omówiliśmy najważniejsze pojęcia programowania obiektowego w Scali:
- Dziedziczenie: Sposób, w jaki podklasy ponownie wykorzystują kod i definiują relację „jest” z nadklasami za pomocą
extends. - Słowo kluczowe
override: Jest niezbędne do jawnego redefiniowania odziedziczonych metod i pól, zapewniając bezpieczeństwo typów i przejrzystość. - Polimorfizm: Możliwość traktowania obiektów różnych podklas jak obiektów wspólnej nadklasy, co umożliwia tworzenie elastycznego i rozszerzalnego kodu.
- Klasy abstrakcyjne: Szablony, których nie można bezpośrednio instancjonować; służą do definiowania wspólnych interfejsów i abstrakcyjnych składowych, które podklasy muszą zaimplementować.
Pojęcia te są niezbędne do tworzenia dobrze uporządkowanych i łatwych w utrzymaniu aplikacji w Scali!
Często zadawane pytania
Czy lekcja „Dziedziczenie i polimorfizm” jest bezpłatna?
Tak — pełny tekst „Dziedziczenie i polimorfizm” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Scala for Backend Engineering & Functional Programming, przejdź na CoddyKit PRO. Kurs Scala for Backend Engineering & Functional Programming zawiera 2 lekcji w sumie.
Co nauczysz się w „Dziedziczenie i polimorfizm”?
Poznaj zasady programowania obiektowego, takie jak dziedziczenie, klasy abstrakcyjne i polimorfizm w Scali. Ćwiczysz Scala for Backend Engineering & Functional Programming z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Scala for Backend Engineering & Functional Programming?
Nie wymagamy żadnego doświadczenia. Scala for Backend Engineering & Functional Programming w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 2.
Ile czasu zajmuje lekcja „Dziedziczenie i polimorfizm”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Scala for Backend Engineering & Functional Programming?
Tak. Każda lekcja Scala for Backend Engineering & Functional Programming zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Klasy, obiekty i pakiety
- Dziedziczenie i polimorfizm