В Объектно-Ориентированное Программирование традиционо существует 3 типа отношений между объектами:

  • Композиция(composition) - отношение между объектами при котором один объект обладает другим (has a). Или можно сказать один объект является часть другого. Время жизни всех объектов одинаковое. Зависит от композитного(основного);
  • Агрегация(aggregation) - отношение между объектами такое же как у композиции. НО время жизни объектов не зависимо;
  • Наследование(inheritance) - отношение «быть»(is a). Наследование можно представить как композицию «один к одному»

Go предлагает механизм встраивания(embedding) как варианта реализации композиции или агрегации.

Embedding As Composition

В Go можно объявить анонимные поля у структур. Указав только тип. Тип поля должен представлять собой именованный тип или указатель на именованный тип. После этого появляется возможность использовать поля и методы встроенного типа.

Начнем с простой структуры:

type Human struct {
   name  string
   age int
}

Тип Human встроен(embedded) в тип Student:

type Student struct {
   Human
   school string
}

Создадим студента:

s := Student{}
fmt.Printf("s = %#v\n", s)
s = Student{Human: Human{name:"", age:0}, school:""}

Такое поле неявно именуются именем типа.

Установить значения для полей можно так:

s := Student{Human{name: "German", age: 18}, "School1"}
s = Student{Human: Human{name:"German", age:18}, school:"School1"}

Добавим метод для Human:

func (h Human) SayHi() {
   fmt.Printf("Hi, I am %s \n", h.name)
}

Его можно вызвать у Student:

s.SayHi()
Hi, I am German

Так же этот метод можно вызвать явно:

s.Human.SayHi()
Hi, I am German

Родительский тип может переопределить метод

Причем метод перекрывается по имени, без учета полной сигнатуры метода:

type Sayer interface {
  SayHi()
}
type Human struct {
  name  string
}
type Student struct {
  Human
}

func (h Human) SayHi() {
  fmt.Printf("Hi, I am %s \n", h.name)
}
// добавлен параметр
func (h Student) SayHi(g string) {
  fmt.Printf("%s, I am %s\n", g, h.name)
}

func Say(s Sayer){
  s.SayHi()
}
h := Human{"German"}
s := Student{Human{"German"}}

h.SayHi() // работает
s.Human.SayHi() // работает
s.SayHi() // not enough arguments in call to s.SayHi
Say(s) // cannot use s (type Student) as type Sayer in argument to Say

Получатель метода(receiver) всегда тип для которого этот метод объявлен.

Методы встроенных типов так же позволяют соответствовать интерфейсам

Метод который принимает интерфейс Sayer и вызывает SayHi:

type Sayer interface {
   SayHi()
}
func Say(s Sayer){
   s.SayHi()
}

Теперь в эту функцию можно передать Student:

s := Student{Human{name: "German", age: 18}, "School1"}
Say(s)
Hi, I am German

Как известно у анонимных структур нельзя объявить методы

Но благодаря встраиванию их можно добавить:

w := struct {
   Human
   id int
}{
   Human: Human{name:"German", age:18},
   id: 1,
}
w.SayHi()

Embedding As Aggregation

Встраивания указателя

Указатель позволяет использовать один экземпляр типа в нескольких других экземплярах типа в которые он встроен. Другими словами, часть данных будет общей для всех экземпляров. В некоторых случаях это может помочь с экономией памяти.

type Student struct {
   *Human
}
h := &Human{"German", 18}
s1 := Student{h}
s2 := Student{h}

Важно помнить, что при неявной инициализации, указатель имеет значение nil, соответственно при его использовании будет panic:

s := Student{}
s.SayHi()
panic: runtime error: invalid memory address or nil pointer dereference

Встраивание интерфейса

Вы можете использовать это, чтобы явно указать, что тип встраивания должен удовлетворять встроенному интерфейсу и в то же время скрывать его данные. В нашем примере можно сделать это следующим образом:

type Sayer interface {
   SayHi()
}
type Student struct {
   Sayer
}
s := Student{Human{"German", 18}}
s.SayHi()
Say(s)
Hi, I am German

Но доступа к полям name и age больше нет.

В стандартной библиотеки это применяется например в sort.Reverse:

type Interface interface {
   Len() int
   Less(i, j int) bool
   Swap(i, j int)
}
type reverse struct {
   // This embedded Interface permits Reverse to use the methods of
   // another Interface implementation.
   Interface
}

// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
   return r.Interface.Less(j, i)
}

// Reverse returns the reverse order for data.
func Reverse(data Interface) Interface {
   return &reverse{data}
}