Menu
Coddy logo textTech

Dlaczego Go nie ma dziedziczenia

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 34 z 107.

Tradycyjne języki obiektowe, takie jak Java i C++, używają dziedziczenia do współdzielenia kodu między typami. Klasa potomna rozszerza klasę nadrzędną, dziedzicząc wszystkie jej pola i metody. Go celowo pomija tę funkcję.

Dziedziczenie powoduje ścisłe powiązanie typów. Gdy zmienia się klasa nadrzędna, zmiany dotyczą wszystkich klas potomnych.

Głębokie hierarchie dziedziczenia stają się trudne do zrozumienia i utrzymania. „Problem kruchej klasy bazowej” występuje, gdy modyfikacje klasy bazowej nieoczekiwanie powodują błędy w klasach pochodnych.

Projektanci Go wybrali inną ścieżkę: kompozycja zamiast dziedziczenia. Zamiast mówić „Dog jest Animal”, Go zachęca do stwierdzenia „Dog ma zachowania podobne do Animal”. Ta subtelna zmiana prowadzi do bardziej elastycznego i łatwiejszego w utrzymaniu kodu.

Go umożliwia ponowne wykorzystanie kodu za pomocą dwóch mechanizmów, które zostały już omówione:

  • Interfejsy definiują kontrakty zachowania bez szczegółów implementacji
  • Osadzanie struktur pozwala typom zawierać inne typy i ponownie wykorzystywać ich metody

Rozważmy takie porównanie. W tradycyjnym programowaniu obiektowym możesz napisać class Dog extends Animal. W Go osadzasz strukturę Animal wewnątrz Dog i implementujesz wspólne interfejsy. Rezultatem jest podobna funkcjonalność przy słabszym sprzężeniu między typami.

W tym rozdziale szczegółowo omówimy osadzanie struktur i pokażemy, jak Go zapewnia korzyści dziedziczenia bez jego wad.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system powiadomień, który pokaże podejście Go oparte na kompozycji zamiast dziedziczenia. Utworzysz typy współdzielące zachowanie za pomocą interfejsów i osadzania struktur, a nie hierarchii klas.

Rozmieścisz kod w trzech plikach:

  • notifier.go: Zdefiniuj interfejs Notifier z jedną metodą Notify(message string) string. Utwórz też strukturę BaseNotifier z polem Name, która będzie osadzana w innych typach. Dodaj do BaseNotifier metodę o nazwie Format(message string) string, która zwraca wiadomość poprzedzoną nazwą powiadamiającego w nawiasach kwadratowych.
  • channels.go: Utwórz dwa typy kanałów powiadomień, które osadzają BaseNotifier i implementują interfejs Notifier:
    • EmailNotifier z dodatkowym polem Address
    • SMSNotifier z dodatkowym polem Phone
    Metoda Notify każdego typu powinna używać osadzonej metody Format i uwzględniać w wyniku informacje dotyczące danego kanału.
  • main.go: Utwórz funkcję o nazwie SendAlert, która przyjmuje dowolny Notifier i wiadomość, a następnie zwraca wynik wywołania Notify. Odczytaj szczegóły powiadomień z wejścia, utwórz oba typy powiadamiających i pokaż, jak można używać ich zamiennie za pośrednictwem interfejsu.

Dane wejściowe będą następujące:

  • Wiersz 1: Nazwa powiadamiającego dla wiadomości e-mail
  • Wiersz 2: Adres e-mail
  • Wiersz 3: Nazwa powiadamiającego dla SMS
  • Wiersz 4: Numer telefonu
  • Wiersz 5: Treść alertu

Metoda Format w BaseNotifier powinna zwracać:

[Name] message

Metody Notify powinny zwracać:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Na przykład dla danych Alerts, user@mail.com, Urgent, 555-1234 i Server down wynik powinien wyglądać następująco:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Zwróć uwagę, że oba typy powiadamiających ponownie wykorzystują metodę Format z BaseNotifier dzięki osadzaniu, a każdy z nich zapewnia własną implementację Notify. Funkcja SendAlert działa z dowolnym Notifier, nie znając jego konkretnego typu. To właśnie kompozycja zamiast dziedziczenia w praktyce.

Spróbuj swoich sił

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert przyjmuje dowolny Notifier i wiadomość oraz zwraca wynik wywołania Notify
// TODO: Zaimplementuj funkcję SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Odczytaj nazwę powiadamiacza e-mail
	scanner.Scan()
	emailName := scanner.Text()
	
	// Odczytaj adres e-mail
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// Odczytaj nazwę powiadamiacza SMS
	scanner.Scan()
	smsName := scanner.Text()
	
	// Odczytaj numer telefonu
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Odczytaj treść alertu
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Utwórz EmailNotifier z emailName i emailAddress
	
	// TODO: Utwórz SMSNotifier z smsName i phoneNumber
	
	// TODO: Użyj SendAlert, aby wysłać alertMessage przez oba powiadamiacze
	// i wypisz wyniki
	
	// Wyłącz ostrzeżenia o nieużywanych zmiennych (usuń te instrukcje, gdy zaczniesz używać zmiennych)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
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