Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Stwó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 interfejs Notifier z jedną metodą Notify(message string) string. Następnie utwórz trzy różne struktury spełniające ten kontrakt:
    • ConsoleNotifier z polem Prefix
    • FileNotifier z polem Filename
    • WebhookNotifier z polem URL
    Każda struktura powinna implementować metodę Notify, aby zwracać ciąg znaków opisujący sposób wysłania powiadomienia.
  • main.go: Utwórz funkcję o nazwie BroadcastAlert, która przyjmuje wycinek wartości typu Notifier oraz ciąg znaków z wiadomością. Ta funkcja powinna wywołać Notify dla 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")
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online