Почему если В golang композиция типов это НЕ НАСЛЕДОВАНИЕ, то структуру-"наследника" можно поместить на то место, где ожидали ВСТРОЕННЫЙ в нее тип
- Почему в golang композиция типов НЕ является НАСЛЕДОВАНИЕМ ?
- Почему Тип B , в который ВСТРОИЛИ в тип А теперь можно подставлять в места, где ожидался тип А ?
- Перефразирую: пусть есть интерфейс ААА и тип А его реализующий. Тип А встроили в тип В с помощью композиции и теперь везде , где ожидался тип/интерфейс ААА можно подставить тип В и компилятор не выдает ошибку , из этого я делаю вывод , что тип В полноценно наследует тип А с его полями и методами (и реализует ААА)
- Почему это неверный вывод?
Рассмотрим пример:
type WrapperWriter struct {
http.ResponseWriter
StatusCode int
}
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
- Метод
next.ServeHTTP(w, r)принимает первым параметром любую структуру, которая реализует интерфейсResponseWriter - Если мы теперь первым параметром вместо
ResponseWriterподставим типWrapperWriterто ошибка не возникнет
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wrapper := &WrapperWriter{
ResponseWriter: w,
StatusCode: 200,
}
next.ServeHTTP(wrapper, r)
})
}
- Я понимаю это так - ошибки нет так как
WrapperWriterэто композицияResponseWriterи на типеWrapperWriterможно вызывать методы типаResponseWriterи НАВЕРНОЕ поэтому компилятор думает чтоWrapperWriterреализуетResponseWriter. Но это не так ведь у методов в ПРИЕМНИКЕ типа стоит по прежнемуResponseWriterи методы вызываются на самом деле уResponseWriter, а не уWrapperWriterдаже если визуально кажется наоборот, из этого следует чтоWrapperWriterНЕ ДОЛЖЕН реализовывать интерфейсResponseWriter. - Тогда почему везде где ожидается
ResponseWriterможно вставитьWrapperWriterи ничего не сломается
Но все это псевдонаследование работает только если в типе
type WrapperWriter struct {
http.ResponseWriter
StatusCode int
}
поле http.ResponseWriter встропено анонимно то есть без имени
- Почему если встроить поле с именем , то тип
WrapperWriterуже нельзя подставлять на место где ожидается тип встроенного поляResponseWriter? и методыResponseWriterуже нельзя напрямую вызвать на типеWrapperWriter
type WrapperWriter struct {
ResponseWriter http.ResponseWriter
StatusCode int
}
Ответы (2 шт):
Это называется promoted embedded field
https://go.dev/ref/spec#Struct_types
A field declared with a type but no explicit field name is called an embedded field. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type or type parameter. The unqualified type name acts as the field name.
...
A field or method f of an embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.
...
If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
А когда ты добавляешь имя, то поле перестаёт быть embedded (ну и, соответственно, перестаёт быть promoted.
Фактически, это переключатель между агрегированием и наследованием.
type WrapperWriter struct {
http.ResponseWriter
StatusCode int
}
в Java будет
class WrapperWriter extends http.ResponseWriter {
int StatusCode;
}
а вот это
type WrapperWriter struct {
Writer http.ResponseWriter
StatusCode int
}
в Java будет
class WrapperWriter {
http.ResponseWriter Writer;
int StatusCode;
}
Видов наследований есть множество, но главное, что объединяет их всех - отношение instance-of. Объект типа-потомка является одновременно объектом типа предка.
Возьмём пример в Java:
public static class A {
public String fieldA;
public A(String fieldA) {
this.fieldA = fieldA;
}
public String getFieldA() {
return fieldA;
}
}
public static class B extends A {
public B(String s) {
super(s);
}
}
public static void main(String[] args) {
A a = new A("Hello");
B b = new B("World");
System.out.println(a.getFieldA()); // Output: Hello
System.out.println(b.getFieldA()); // Output: World
A aa = b;
}
Компилируется без ошибок.
В Go отношение instance-of по отношению к "предку" нарушено.
type A struct {
FieldA string
}
func (a A) MethodA() string {
return a.FieldA
}
type B struct {
A
}
func main() {
b := B{
A: A{FieldA: "Hello"},
}
var a A = b
}
На присваивании a = b компилятор Го выругается, в отличие от компилятора Java в аналогичной ситуации.
Но при этом все интерфейсы, которые реализуют типы A и *A, реализуют и типы B и *B. Это называется duck typing - ходит как утка, крякает как утка - значит, утка. "Наследование интерфейсов" достигается за счёт того, что в Go релизация интерфейсов не декларативная, как в Java, а по факту:
- Java: для того, чтобы тип A реализовал интерфейс I, необходимо это декларировать
class A implements I. - Go: для того, чтобы тип А реализовал интерфейс I, небходимо, чтобы в method set типа A наличествовали все методы из интерфейса I.
А теперь следите за руками. Если поле включено в структуру без имени, то методы типа этого поля включаются в method set самой структуры (а методы с получателем-указателем включаются в method set указателя на структуру). Следовательно, если тип A реализовал интерфейс I, то тип struct{ A } тоже реализует этот интерфейс, ведь методы A входят в набор методов "наследника", и тем самым "наследник" реализует интерфейс.
Это работает даже в тех случаях, когда встраивают интерфейс.
package main
import "fmt"
type A interface {
MethodA() string
}
type B struct {
A
}
type AImpl struct {
FieldA string
}
func (a *AImpl) MethodA() string {
return a.FieldA
}
func main() {
b := B{
A: &AImpl{FieldA: "Hello"},
}
fmt.Println(b.MethodA())
}
Единственное, что нужно соблюдать при встраивании интефейсов - инциализация. Вы должны подставить на место встроенного поля объект, тип которого реализует интерфейс. В этом примере поле B.A является интерфейсом, поэтому нужно подставить какой-то конкретный объект - &AImpl{}, так как интерфейс A реализует тип *AImpl.
По этой схеме реализован тип WrapperWriter. Благодаря встраиванию типа http.ResponseWriter сам WrapperWriter реализует этот интерфейс.
Но сама по себе реализация странная. Так как метод Write([]byte) (int, error) на самом деле реализуется полем WrapperWriter.ResponseWriter, то поле StatusCode этот метод игнорирует. Бессмыслица какая-то.