Duck typing w Go
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 42 z 107.
Termin duck typing pochodzi od powiedzenia: „Jeśli chodzi jak kaczka i kwacze jak kaczka, to jest kaczką”. W Go oznacza to, że typ nie musi jawnie deklarować, że implementuje interfejs. Wystarczy, że ma odpowiednie metody.
Rozważ ten interfejs i dwa całkowicie niezwiązane ze sobą typy:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
W definicjach ani Duck, ani Robot nie ma nigdzie wzmianki o Quacker. Mimo to oba spełniają interfejs, ponieważ mają metodę Quack() o prawidłowej sygnaturze:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Ta niejawna zgodność jest potężna, ponieważ pozwala definiować interfejsy, gdy typy już istnieją. Możesz utworzyć interfejs pasujący do typów z zewnętrznych pakietów bez modyfikowania ich kodu źródłowego. Typy nie muszą wiedzieć o istnieniu Twojego interfejsu. Wystarczy, że zachowują się poprawnie.
Duck typing w Go zapewnia elastyczność języków dynamicznych, a jednocześnie zachowuje bezpieczeństwo typów na etapie kompilacji. Kompilator sprawdza, czy typy rzeczywiście mają wymagane metody, zanim pozwoli używać ich jako wartości interfejsu.
Wyzwanie
ŁatwyZbudujmy system wiadomości, który pokaże duck typing w praktyce. Utworzysz całkowicie niezwiązane ze sobą typy, które mogą wysyłać wiadomości za pośrednictwem wspólnego interfejsu, mimo że żaden z nich nie deklaruje jawnie, że go implementuje.
Rozmieścisz kod w trzech plikach:
messengers.go: Utwórz trzy niezwiązane ze sobą struktury, które przypadkiem mają takie samo zachowanie:Phonez polemNumber. Jego metodaSendMessage(text string) stringzwracaSMS to [Number]: [text]Computerz polemEmail. Jego metodaSendMessage(text string) stringzwracaEmail to [Email]: [text]Pigeonz polemName. Jego metodaSendMessage(text string) stringzwraca[Name] carries: [text]
sender.go: Zdefiniuj interfejsMessenger, który wymaga metodySendMessage(text string) string. Utwórz funkcję o nazwieBroadcast, która przyjmuje wycinek wartości typuMessengeroraz wiadomość w postaci ciągu znaków, a następnie zwraca wycinek ciągów znaków zawierający wynik wywołaniaSendMessagedla każdego komunikatora.main.go: Odczytaj dane wejściowe, utwórz po jednym obiekcie każdego typu komunikatora, zbierz je w wycinku wartości typuMessengeri użyjBroadcast, aby wysłać wiadomość za pośrednictwem wszystkich komunikatorów. Wypisz każdy wynik w osobnym wierszu.
Podane zostaną następujące dane wejściowe:
- Wiersz 1: numer telefonu
- Wiersz 2: adres e-mail
- Wiersz 3: imię gołębia
- Wiersz 4: wiadomość do rozesłania
Na przykład dla wartości 555-1234, alice@mail.com, Winston i Hello World wynik powinien wyglądać następująco:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldKluczowy wniosek jest taki, że Phone, Computer i Pigeon nie mają ze sobą nic wspólnego. Nie współdzielą typu bazowego ani jawnie nie implementują interfejsu Messenger. Jednak ponieważ wszystkie „kwaczą” tak samo (mają zgodną metodę), Go pozwala używać ich jako wartości typu Messenger. Tak właśnie działa duck typing.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Wczytaj numer telefonu
scanner.Scan()
phoneNumber := scanner.Text()
// Wczytaj adres e-mail
scanner.Scan()
email := scanner.Text()
// Wczytaj imię gołębia
scanner.Scan()
pigeonName := scanner.Text()
// Wczytaj wiadomość do rozesłania
scanner.Scan()
message := scanner.Text()
// TODO: Utwórz po jednym obiekcie każdego typu posłańca (Phone, Computer, Pigeon)
// TODO: Zbierz wszystkich posłańców w slice typu Messenger
// TODO: Użyj Broadcast, aby wysłać wiadomość za pośrednictwem wszystkich posłańców
// TODO: Wypisz każdy wynik w osobnym wierszu
// Kod tymczasowy wykorzystujący zmienne (usuń podczas implementacji)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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