Osadzanie a agregacja
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 38 z 107.
Dowiedziałeś się, że osadzanie udostępnia pola i metody w zewnętrznej strukturze. Go obsługuje jednak również agregację, w której dołączasz inną strukturę jako nazwane pole. Zrozumienie, kiedy używać każdego z tych podejść, jest kluczowe dla dobrego projektowania.
Przy osadzaniu pomijasz nazwę pola, a składowe typu wewnętrznego stają się bezpośrednio dostępne:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // osadzanie — bez nazwy pola
}
d := Document{}
d.Write() // bezpośredni dostęp
W przypadku agregacji nadajesz polu jawną nazwę, która jest wymagana do uzyskania dostępu do typu wewnętrznego:
type Document struct {
writer Writer // agregacja — pole nazwane
}
d := Document{writer: Writer{}}
d.writer.Write() // trzeba użyć nazwy pola
Kluczowa różnica polega na relacji, którą wyrażasz. Dziedziczenie sugeruje relację „jest” (is-a), w której typ zewnętrzny przyjmuje tożsamość typu wewnętrznego. Agregacja sugeruje relację „ma” (has-a), w której typ zewnętrzny jedynie zawiera typ wewnętrzny.
Użyj osadzania, gdy chcesz, aby typ zewnętrzny spełniał interfejsy zaimplementowane przez typ wewnętrzny lub gdy bezpośredni dostęp do metod sprawia, że API jest czytelniejsze. Użyj agregacji, gdy typ wewnętrzny jest szczegółem implementacyjnym, który nie powinien być udostępniany, lub gdy chcesz mieć jawny wpływ na sposób dostępu do typu wewnętrznego.
Wyzwanie
ŁatwyZbudujmy system generowania raportów, który pokaże, kiedy używać osadzania, a kiedy agregacji. Utworzysz system, w którym niektóre komponenty powinny być bezpośrednio dostępne (osadzanie), a inne powinny pozostać wewnętrznymi szczegółami implementacji (agregacja).
Podzielisz kod na trzy pliki:
formatter.go: Utwórz strukturęFormatterz polemStyle(string) oraz metodąFormat(text string) string, która zwraca tekst otoczony określonym stylem, na przykład[Style] text [/Style]. Będzie to wewnętrzna funkcja pomocnicza, która nie powinna być bezpośrednio udostępniana.report.go: Utwórz dwie struktury, które obsługują generowanie raportów na różne sposoby:- Strukturę
Metadataz polamiAuthoriDate. Reprezentuje ona publiczne informacje, które powinny być bezpośrednio dostępne - Strukturę
Reportz polemTitle, która osadzaMetadata(aby zapewnić bezpośredni dostęp do autora i daty), ale używa agregacji dlaFormatter(jako wewnętrznego szczegółu implementacji z nazwanym polemformatter)
ReportmetodęGenerate() string, która używa wewnętrznego formatera do formatowania tytułu i uwzględnia metadane.- Strukturę
main.go: Odczytaj szczegóły raportu z wejścia, utwórzReportz osadzonymi metadanymi i agregowanym formatterem. Pokaż różnicę, uzyskując dostęp do autora bezpośrednio przez raport (za pomocą osadzania), a następnie generując sformatowany raport (który wewnętrznie używa ukrytego formatera).
Dane wejściowe będą podane w następującej kolejności:
- Wiersz 1: Tytuł raportu
- Wiersz 2: Imię i nazwisko autora
- Wiersz 3: Data
- Wiersz 4: Styl formatera
Twoja metoda Format powinna zwracać:
[Style] text [/Style]Twoja metoda Generate powinna zwracać:
[formatted title]
Author: [Author], Date: [Date]W pliku głównym wypisz trzy wiersze:
- Autora pobranego bezpośrednio z Report (pokazując, że osadzanie udostępnia pole)
- Datę pobraną za pośrednictwem nazwy osadzonego typu (
report.Metadata.Date) - Wynik wywołania
Generate()
Na przykład dla danych Quarterly Results, Jane Doe, 2024-01-15 i BOLD wynikiem powinno być:
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15Zwróć uwagę, że dostęp do Author i Date jest możliwy bezpośrednio przez Report dzięki osadzaniu, natomiast formatter pozostaje ukryty. Nie możesz bezpośrednio wywołać report.Format(), ponieważ jest on agregowany za pomocą nazwanego pola. To praktyczny przykład relacji „jest” i „ma”.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: Utwórz strukturę Metadata z autorem i datą
// TODO: Utwórz Formatter z podanym stylem
// TODO: Utwórz Report, który osadza Metadata i agreguje Formatter
// TODO: Wypisz autora, odwołując się do niego bezpośrednio przez Report (osadzenie udostępnia pole)
// TODO: Wypisz datę, odwołując się do niej przez nazwę osadzonego typu (report.Metadata.Date)
// TODO: Wypisz wynik wywołania Generate()
// Wyłącz ostrzeżenia o nieużywanych zmiennych (usuń te instrukcje, gdy zaczniesz używać zmiennych)
_ = title
_ = author
_ = date
_ = style
}
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