Menu

Osadzanie struktur w Golang: embedding i promowane metody

Osadzanie (embedding) umieszcza jeden typ w drugim bez nazwy pola, więc jego pola i metody są promowane do typu zewnętrznego. Zobacz, jak działa promocja, osadzanie interfejsów, konflikty nazw i dlaczego osadzanie nie jest dziedziczeniem.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Osadzanie w jednym przykładzie

Pole z typem, ale bez nazwy, to pole osadzone. Pola i metody osadzonego typu stają się dostępne bezpośrednio na typie zewnętrznym.

Wynik:

Ana
Hi, I'm Ana
ana@example.com
{User:{Name:Ana Email:ana@example.com} Level:2}

Nazwą osadzonego pola jest nazwa jego typu, User. Tak odwołujesz się do niego w literałach (User: User{...}) i wtedy, gdy potrzebujesz samej wewnętrznej wartości (a.User). Promowanego pola nie można ustawić w literale po krótkiej nazwie: Admin{Name: "Ana"} kończy się błędem unknown field Name in struct literal of type Admin.

Promowane metody spełniają interfejsy

Promocja dodaje metody osadzonego typu do zbioru metod typu zewnętrznego. Dlatego typ zewnętrzny spełnia każdy interfejs, który spełnia typ wewnętrzny.

Zasady zbiorów metod wynikają z osadzonego typu. Osadzenie T promuje metody T z odbiorcą przez wartość zarówno do zewnętrznej wartości, jak i do zewnętrznego wskaźnika, ale metody *T z odbiorcą wskaźnikowym tylko do zewnętrznego wskaźnika. Osadzenie *T promuje oba zbiory do obu. W razie wątpliwości osadzaj wskaźnik albo używaj typu zewnętrznego przez wskaźnik.

Osadzenie wskaźnika ma swój koszt: wskaźnik może być nil. Service{} bez Logger się kompiluje, a svc.Log(...) wywołuje wtedy panic.

Osadzanie to nie dziedziczenie

Osadzanie wygląda jak tworzenie podklasy, ale trzy rzeczy działają inaczej niż w Javie czy Pythonie.

Typ zewnętrzny nie jest typem wewnętrznym. Admin nie jest User. Funkcja przyjmująca User nie przyjmie Admin; przekaż a.User.

Nie ma wirtualnego wywoływania metod. Promowana metoda działa na osadzonej wartości i nic nie wie o typie zewnętrznym. Jeśli wywołuje inną metodę, wywołuje wersję swojego typu, nawet gdy typ zewnętrzny definiuje metodę o tej samej nazwie.

Wynik:

woof
The animal says ...

W języku opartym na klasach Speak wypisałoby „woof”. W Go d.Speak() to skrót od d.Animal.Speak(), a odbiorcą tego wywołania jest Animal. Gdy chcesz zachowania zależnego od typu, użyj interfejsu: przekaż Sounder do kodu, który go potrzebuje.

Typ wewnętrzny nie wie, że jest osadzony. Nie ma super. Aby rozszerzyć promowaną metodę, zdefiniuj metodę o tej samej nazwie na typie zewnętrznym i jawnie wywołaj wewnętrzną:

func (a Admin) Greet() string {
	return a.User.Greet() + " (admin)"
}

Konflikty nazw i przesłanianie

Pole lub metoda typu zewnętrznego przesłania promowane pole lub metodę o tej samej nazwie, tak jak zrobiło to Dog.Sound powyżej. Wygrywa nazwa położona płycej.

Gdy dwa osadzone typy na tej samej głębokości promują tę samą nazwę, staje się ona niejednoznaczna. Program nadal się kompiluje, dopóki nic nie używa niejednoznacznej nazwy:

Rozwiąż konflikt, używając pełnej ścieżki albo definiując ID na typie zewnętrznym.

Osadzanie interfejsów

Interfejsy mogą osadzać inne interfejsy. Biblioteka standardowa w ten sposób buduje większe interfejsy:

type ReadWriter interface {
	Reader
	Writer
}

Struktura też może osadzić interfejs. Spełnia wtedy ten interfejs dzięki wartości zapisanej w polu, a ty nadpisujesz tylko metody, które cię interesują. To wzorzec dekoratora prawie bez kodu:

Jawne c.Reader.Read(p) jest konieczne. Napisanie c.Read(p) wewnątrz Read wywoływałoby tę samą metodę w nieskończoność.

Osadzenie interfejsu w atrapie testowej to popularny skrót: osadź duży interfejs, zaimplementuj jedną metodę potrzebną w teście i zostaw resztę. Każda niezaimplementowana metoda po wywołaniu kończy się panic z dereferencją wskaźnika nil, a w teście często właśnie o to chodzi.

Częste zastosowanie w praktyce: osadzanie sync.Mutex

type Stats struct {
	sync.Mutex
	hits map[string]int
}

func (s *Stats) Hit(page string) {
	s.Lock()
	defer s.Unlock()
	s.hits[page]++
}

To czyta się dobrze, ale eksportuje też Lock i Unlock jako część API Stats, więc każdy wywołujący może zablokować twoją strukturę. Dla typów używanych poza pakietem nazwane pole (mu sync.Mutex) utrzymuje blokadę jako prywatną. Ten kompromis dotyczy każdego osadzania: wszystko, co eksportuje osadzony typ, staje się częścią publicznego interfejsu twojego typu.

Typowe błędy

  • Oczekiwanie wirtualnego wywoływania metod. Promowane metody nigdy nie wywołują nadpisanych wersji z typu zewnętrznego.
  • Ustawianie promowanych pól w literale. Użyj nazwy osadzonego typu: Admin{User: User{Name: "Ana"}}.
  • Osadzone wskaźniki nil. Osadzony *T lub interfejs musi być ustawiony, zanim wywołasz jego metody.
  • Przypadkowe rozszerzenie API. Osadzanie eksportuje na twoim typie wszystkie eksportowane metody typu wewnętrznego.

Najczęściej zadawane pytania

Czym jest osadzanie struktur w Go?

To zadeklarowanie pola samym typem, bez nazwy: type Admin struct { User; Level int }. Pola i metody osadzonego User są promowane, więc a.Name i a.Greet() działają bezpośrednio na Admin. Osadzona wartość nadal jest zwykłym polem, dostępnym jako a.User.

Czy Go ma dziedziczenie?

Nie. Go nie ma klas ani dziedziczenia podtypów. Osadzanie daje ponowne użycie kodu przez kompozycję: typ zewnętrzny dostaje metody typu wewnętrznego, ale Admin nie jest User. Nie możesz przekazać Admin tam, gdzie oczekiwany jest User, a promowane metody nie mogą wywoływać metod typu zewnętrznego. Polimorfizm w Go zapewniają interfejsy.

Jak zainicjalizować osadzoną strukturę w Go?

W literale złożonym nazwij osadzone pole nazwą jego typu: Admin{User: User{Name: "Ana"}, Level: 2}. W literale nie można bezpośrednio ustawić promowanych pól: Admin{Name: "Ana"} kończy się błędem unknown field Name in struct literal of type Admin.

Czy w Go można osadzić interfejs w strukturze?

Tak. Struktura spełnia wtedy interfejs dzięki osadzonej wartości, a ty możesz nadpisać wybrane metody. To częste w dekoratorach i atrapach testowych. Jeśli osadzone pole interfejsu jest nil, wywołanie metody, której nie nadpisano, kończy się panic z dereferencją wskaźnika nil.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ