Historia pytania:
Go wdrożył prostą, ale potężną koncepcję metod i interfejsów opartą na typowaniu kaczkowym. Metody można przypisywać tylko do typów nazwanych (struct lub alias), a interfejsy są zbiorem metod. To klucz do polimorfizmu i abstrakcji bez konieczności jawnego wskazywania implements.
Problem:
Programiści, którzy przybyli z Javy lub C#, często oczekują jawnych słów kluczowych implements lub extends i mylą się w różnicach między metodami z odbiornikami przez wartość i przez wskaźnik, co prowadzi do nieprzewidywalnego zachowania i błędów przy realizacji interfejsów.
Rozwiązanie:
Definicja metody i interfejsu:
type User struct { Name string } func (u User) Greet() string { return "Hello, " + u.Name } func (u *User) SetName(name string) { u.Name = name } type Greeter interface { Greet() string }
Kluczowe cechy:
Czy można zadeklarować metody dla typu aliasu z podstawowym typem int lub string?
Tak, jeśli jest to typ nazwany, na przykład:
type MyInt int func (m MyInt) Double() int { return int(m) * 2 }
Czym się różni realizacja interfejsu przez metody z wskaźnikiem i wartością?
Jeśli metoda interfejsu jest zadeklarowana jako (T), to oba T i *T realizują interfejs. Jeśli (*T), tylko *T spełnia interfejs. To kluczowe dla przekazywania struktury do funkcji, które przyjmują interfejs.
Jak działa "zero value" dla interfejsu i co się stanie, jeśli wywołasz metodę na wartości nil?
Zmienna interfejsowa bez inicjalizacji jest równa nil, próba wywołania metody na nil-pole spowoduje panikę, jeśli nie została wdrożona jawna obsługa wskaźnika nil.
Typ został zadeklarowany z metodami przez wskaźnik (*T), a interfejs oczekuje metod przez wartość (T), struktura nie kompiluje się przy próbie użycia w interfejsach. Próba deklaracji metody dla zmiennej typu []User, a nie dla typu User.
Zalety:
Wady:
Wszystkie metody realizujące interfejsy są zadeklarowane z poprawnym odbiornikiem. Nie używane są interfejsy tam, gdzie nie są potrzebne (typ konkretny tam, gdzie można, polimorfizm tam, gdzie to konieczne).
Zalety:
Wady: