Interfejs jako kontrakt
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 27 z 107.
Myślenie o interfejsach jako o kontraktach pomaga projektować lepsze programy w Go. Gdy funkcja przyjmuje typ interfejsowy, mówi: „Nie obchodzi mnie, jaki typ konkretny mi podasz, o ile spełnia ten kontrakt”.
Myślenie oparte na kontraktach pozwala pisać funkcje, które koncentrują się na zachowaniu, a nie na konkretnych typach. Rozważ funkcję, która musi przetwarzać dane z różnych źródeł:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}Funkcja ProcessData nie wie ani nie musi wiedzieć, czy otrzymuje połączenie z bazą danych, klienta API czytnik plików. Wymaga jedynie, aby dane wejściowe umożliwiały wywołanie FetchData(). Każdy typ spełniający ten kontrakt będzie działać.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // Działa!
ProcessData(api) // Też działa!
}Takie podejście oparte na kontrakcie sprawia również, że kod łatwiej testować. Podczas testowania możesz utworzyć typ zastępczy, który spełnia wymagania interfejsu, bez potrzeby korzystania z prawdziwych baz danych ani połączeń sieciowych. Testowana funkcja nie zauważa różnicy, ponieważ kontrakt jest spełniony.
Wyzwanie
ŁatwyStwórzmy system powiadomień, który pokazuje, jak interfejsy pełnią rolę kontraktów. Utworzysz interfejs Notifier, który określa, na czym polega wysyłanie powiadomienia, a następnie zaimplementujesz wiele kanałów powiadomień, które będą przestrzegać tego kontraktu.
Rozmieścisz kod w dwóch plikach:
notifiers.go: Zdefiniuj interfejsNotifierz jedną metodąNotify(message string) string. Następnie utwórz trzy różne struktury spełniające ten kontrakt:ConsoleNotifierz polemPrefixFileNotifierz polemFilenameWebhookNotifierz polemURL
Notify, aby zwracać ciąg znaków opisujący sposób wysłania powiadomienia.main.go: Utwórz funkcję o nazwieBroadcastAlert, która przyjmuje wycinek wartości typuNotifieroraz ciąg znaków z wiadomością. Ta funkcja powinna wywołaćNotifydla każdego powiadamiacza i wyświetlić wyniki. Odczytaj konfigurację z wejścia, utwórz wszystkie trzy typy powiadamiaczy i rozgłoś alert za ich pośrednictwem.
Zostaną podane następujące dane wejściowe:
- Wiersz 1: Prefiks konsoli
- Wiersz 2: Nazwa pliku
- Wiersz 3: Adres URL webhooka
- Wiersz 4: Treść alertu
Metody Notify powinny zwracać ciągi znaków w następujących formatach:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Na przykład, dla danych ALERT:, log.txt, https://hooks.example.com i Server down! wynik powinien wyglądać następująco:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!Zaletą tego rozwiązania jest to, że Twoja funkcja BroadcastAlert nie musi znać ani uwzględniać konkretnych typów powiadamiaczy. Wymaga jedynie, aby każdy z nich spełniał kontrakt Notifier. Później możesz dodać SlackNotifier lub EmailNotifier bez żadnych zmian w funkcji rozgłaszającej.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert wysyła wiadomość za pośrednictwem wszystkich obiektów powiadamiających
// TODO: Zaimplementuj tę funkcję tak, aby:
// - Przyjmowała wycinek wartości Notifier oraz ciąg znaków z wiadomością
// - Wywoływała Notify dla każdego obiektu powiadamiającego i wypisywała wyniki
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Przejdź przez wszystkie obiekty powiadamiające i wypisz wynik każdego powiadomienia
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Wczytaj prefiks konsoli
scanner.Scan()
prefix := scanner.Text()
// Wczytaj nazwę pliku
scanner.Scan()
filename := scanner.Text()
// Wczytaj adres URL webhooka
scanner.Scan()
url := scanner.Text()
// Wczytaj treść alertu
scanner.Scan()
message := scanner.Text()
// TODO: Utwórz instancje wszystkich trzech typów obiektów powiadamiających
// - ConsoleNotifier z prefiksem
// - FileNotifier z nazwą pliku
// - WebhookNotifier z adresem URL
// TODO: Utwórz wycinek wartości typu Notifier zawierający wszystkie trzy obiekty powiadamiające
// TODO: Wywołaj BroadcastAlert z obiektami powiadamiającymi i wiadomością
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
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