Implementacja niejawna
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 26 z 107.
W wielu językach obiektowych, takich jak Java czy C#, musisz jawnie zadeklarować, że typ implementuje interfejs, używając słów kluczowych takich jak implements. Go podchodzi do tego inaczej: implementacja interfejsu jest niejawna.
Typ spełnia wymagania interfejsu po prostu przez zaimplementowanie wszystkich jego metod. Nie trzeba nigdzie deklarować tej relacji. Kompilator ustala ją automatycznie.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter niejawnie implementuje Writer
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Zauważ, że w FileWriter nie ma deklaracji implements Writer. Ponieważ FileWriter ma metodę Write(data string) int zgodną z sygnaturą interfejsu, automatycznie spełnia wymagania interfejsu Writer.
Ten projekt ma potężną konsekwencję: możesz definiować interfejsy, gdy typy już istnieją. Jeśli biblioteka innej firmy ma typ z metodami, których potrzebujesz, możesz utworzyć interfejs, który ten typ już spełnia, bez modyfikowania oryginalnego kodu.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Działa, ponieważ FileWriter spełnia wymagania interfejsu Writer
}
Implementacja niejawna sprawia, że kod Go jest luźno powiązany i elastyczny. Typy nie muszą z wyprzedzeniem znać interfejsów, co ułatwia komponowanie systemów z niezależnych części.
Wyzwanie
ŁatwyZbudujmy system dostarczania wiadomości, który pokazuje, jak typy w Go niejawnie spełniają interfejsy. Utworzysz różne typy komunikatorów, które wszystkie spełniają ten sam interfejs, bez jawnego deklarowania tej relacji.
Podzielisz kod na dwa pliki:
messengers.go: Zdefiniuj interfejsMessengerz jedną metodąSend(message string) string. Następnie utwórz dwie struktury, które będą niejawnie spełniać ten interfejs:EmailMessengerz polemAddress(string)SMSMessengerz polemPhoneNumber(string)
Send, która zwraca ciąg znaków opisujący dostarczenie wiadomości. Żadna ze struktur nie powinna jawnie deklarować, że implementujeMessenger: Go ustali to automatycznie na podstawie sygnatur metod.main.go: Utwórz funkcję o nazwieDeliverMessage, która przyjmuje dowolny element typuMessengeroraz ciąg znaków z wiadomością, a następnie zwraca wynik wywołaniaSend. Odczytaj informacje kontaktowe z wejścia, utwórz oba typy komunikatorów i użyj swojej funkcji, aby dostarczyć wiadomości za pośrednictwem każdego z nich.
Zostaną podane następujące dane wejściowe:
- Wiersz 1: Adres e-mail
- Wiersz 2: Numer telefonu
- Wiersz 3: Wiadomość do wysłania
Metody Send powinny zwracać ciągi znaków w następujących formatach:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Na przykład dla danych alice@example.com, 555-1234 i Hello! wynik powinien wyglądać następująco:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!Najważniejsza kwestia jest taka, że funkcja DeliverMessage przyjmuje dowolny element typu Messenger, a zarówno EmailMessenger, jak i SMSMessenger spełniają ten interfejs po prostu dzięki posiadaniu zgodnej metody Send. Nigdzie nie ma słowa kluczowego implements: niejawna implementacja w Go obsługuje wszystko.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Utwórz funkcję DeliverMessage, która przyjmuje dowolny Messenger
// oraz ciąg znaków z wiadomością, a następnie zwraca wynik wywołania Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj adres e-mail
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Odczytaj numer telefonu
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Odczytaj wiadomość
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Utwórz EmailMessenger z adresem e-mail
// TODO: Utwórz SMSMessenger z numerem telefonu
// TODO: Użyj DeliverMessage, aby wysłać wiadomość za pośrednictwem każdego obiektu Messenger
// i wypisz wyniki
}
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