В Объектно-Ориентированное Программирование традиционо существует 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}
}