Polimorfizm za pomocą interfejsów
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 41 z 107.
Polimorfizm pozwala traktować różne typy w jednolity sposób za pomocą wspólnego interfejsu. W Go osiąga się to wyłącznie za pomocą interfejsów, bez dziedziczenia ani hierarchii klas.
Gdy funkcja przyjmuje typ interfejsu jako parametr, można jej przekazać dowolny typ konkretny, który implementuje ten interfejs. Funkcja nie musi znać konkretnego typu. Interesuje ją tylko zachowanie zdefiniowane przez interfejs:
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
Teraz MakeSound działa z każdym typem, który ma metodę Speak():
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Hau!
MakeSound(cat) // Miau!
}
To samo wywołanie funkcji daje różne rezultaty w zależności od przekazanego rzeczywistego typu. To właśnie polimorfizm w działaniu. Funkcja MakeSound jest napisana raz, ale działa z nieograniczoną liczbą typów, o ile spełniają one interfejs Speaker.
To podejście sprawia, że Twój kod jest elastyczny i łatwy do rozbudowy. Dodanie nowego typu, który mówi, nie wymaga zmian w istniejących funkcjach — wystarczy zaimplementować interfejs, a wszystko będzie działać automatycznie.
Wyzwanie
ŁatwyZbudujmy system opisu pojazdów, który pokaże polimorfizm w działaniu. Utworzysz różne typy pojazdów, które współdzielą wspólne zachowanie za pośrednictwem interfejsu, a następnie napiszesz jedną funkcję działającą z dowolnym pojazdem.
Rozmieścisz kod w dwóch plikach:
vehicles.go: Zdefiniuj interfejsDescriber, który wymaga metodyDescribe() string. Następnie utwórz trzy typy pojazdów, z których każdy implementuje ten interfejs na swój sposób:Carz polamiBrandiModel. Jego metodaDescribe()zwracaCar: [Brand] [Model]Motorcyclez polamiBrandiEngineCC(int). Jego metodaDescribe()zwracaMotorcycle: [Brand] [EngineCC]ccBicyclez polemType(np. „Mountain” lub „Road”). Jego metodaDescribe()zwracaBicycle: [Type]
main.go: Utwórz funkcję o nazwiePrintDescription, która przyjmuje dowolny obiekt implementującyDescriberi wyświetla wynik wywołaniaDescribe(). Odczytaj z wejścia szczegóły pojazdów, utwórz po jednym obiekcie każdego typu pojazdu i przekaż każdy z nich doPrintDescription, aby pokazać, że ta sama funkcja działa z wszystkimi trzema różnymi typami.
Podane zostaną następujące dane wejściowe:
- Wiersz 1: Marka samochodu
- Wiersz 2: Model samochodu
- Wiersz 3: Marka motocykla
- Wiersz 4: Pojemność silnika motocykla w CC (liczba całkowita)
- Wiersz 5: Typ roweru
Na przykład dla danych Toyota, Camry, Honda, 600 i Mountain wynik powinien wyglądać następująco:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainZauważ, że PrintDescription nie musi wiedzieć, czy otrzymuje obiekt typu Car, Motorcycle czy Bicycle. Po prostu wywołuje Describe(), a każdy typ zwraca własny, unikalny wynik. To właśnie polimorfizm: jedna funkcja, wiele zachowań.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// TODO: Utwórz funkcję o nazwie PrintDescription, która przyjmuje dowolny obiekt typu Describer
// i wypisuje wynik wywołania Describe()
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj dane samochodu
carBrand, _ := reader.ReadString('\n')
carBrand = strings.TrimSpace(carBrand)
carModel, _ := reader.ReadString('\n')
carModel = strings.TrimSpace(carModel)
// Odczytaj dane motocykla
motoBrand, _ := reader.ReadString('\n')
motoBrand = strings.TrimSpace(motoBrand)
motoEngineStr, _ := reader.ReadString('\n')
motoEngineStr = strings.TrimSpace(motoEngineStr)
motoEngine, _ := strconv.Atoi(motoEngineStr)
// Odczytaj dane roweru
bicycleType, _ := reader.ReadString('\n')
bicycleType = strings.TrimSpace(bicycleType)
// TODO: Utwórz obiekty Car, Motorcycle i Bicycle, używając wartości wejściowych
// TODO: Wywołaj PrintDescription dla każdego pojazdu, aby zademonstrować polimorfizm
fmt.Println("TODO: Print vehicle descriptions")
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online