#16 golang Методы - функции для структур и других получателей

Метод — это функция, которая может быть привязана к определённому типу данных. Такой тип данных называется получателем ()

В основе этих типов могут лежать как структуры, так и int, string и любые другие доступные типы в Go.

Привязывание методов к структурам

Для начала рассмотрим методы, которые привязываются к структурам.

Определим именованный тип Person и пару методов к нему, а также проверим как они работают:

type Person struct {
	Id   int
	Name string
}

// Получатель - сама структура
// Не изменяет копию структуры
func (receiver Person) updateName(name string) {
	receiver.Name = name
}

// Получатель - указатель на структуру.
// Изменяет оригинальную структуру
func (receiver *Person) setName(name string) {
	receiver.Name = name
}

func main() {
	Vasya := Person{
		Id:   1,
		Name: "Вася",
	}

	Vasya.updateName("Василий")
	fmt.Println(Vasya) // {1 Вася}
	Vasya.setName("Василий")
	fmt.Println(Vasya) // {1 Василий}
}

-- из примера видно, что для того, чтобы реально менять поля структуры с помощью методов нам необходимо указывать получателем указатель на структуру, если же мы указываем просто саму структуру, то функция работает с её копией.

Автоматическое "понимание" типа получателя - "по ссылке" или по значению

Еще в примере выше интересно то, что для setName() мы берем не указатель на структуру, а саму переменную структуры - Go разруливает это ситуацию автоматически, так если мы возьмем указатели в обоих случаях из примера выше и вызовем на них методы, то поведение не изменится:

type Person struct {
	Id   int
	Name string
}

// Получатель - сама структура
// Не изменяет оригинальную структуру
func (receiver Person) updateName(name string) {
	receiver.Name = name
}

// Получатель - указатель на структуру.
// Изменяет оригинальную структуру
func (receiver *Person) setName(name string) {
	receiver.Name = name
}

func main() {
	Vasya := Person{
		Id:   1,
		Name: "Вася",
	}

	// попобруем теперь с указателями
	(&Vasya).updateName("Василий")
	fmt.Println(Vasya) // {1 Вася}
	(&Vasya).setName("Василий")
	fmt.Println(Vasya) // {1 Василий}
}

"Наследование" методов в структурах

Подобно получению доступа к вложенным полям в структурах при их композиции, можно получать/передавать методы.

Рассмотрим пример сразу со встроенной структурой (в отличии от именованной композии тут всё более запутанно):

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

func (receiver *Person) setName(name string) {
	receiver.Name = name
}

func (receiver *Person) setOwnName(name string) {
	receiver.Name = name
}

type Address struct {
	Address string
	Name    string // еще одно поле с именем Name
	Person         // встроенная структура
}

func (receiver *Address) setName(name string) {
	receiver.Name = name
}

func main() {
	myAddress := Address{
		Address: "Лизюкова",
		Name:    "Мой адрес",
		Person: Person{
			Id:       1,
			Name:     "Петя",
			IsActive: true,
		},
	}

	fmt.Println(myAddress)
	// {Лизюкова Мой адрес {1 Петя true}}

	myAddress.setName("Как бы мой адрес")
	fmt.Println(myAddress)
	// {Лизюкова Как бы мой адрес {1 Петя true}}

	// неперекрытый вложенный метод доступен на первом уровне:
	myAddress.setOwnName("Вася")
	fmt.Println(myAddress)
	// {Лизюкова Как бы мой адрес {1 Петя true}}

	// обращение к перерытому имени через полный селектор
	myAddress.Person.setName("Саша")
	fmt.Println(myAddress)
	// {Лизюкова Как бы мой адрес {1 Саша true}}
}

-- подобно ситуации с полями, при перекрытии имен выбирается первым делом метод на верхнем уровне (см. выше какой именно из двух методов setName() отработал)

Также в примере выше следует обратить внимание на строку:

myAddress.Person.setOwnName("Саша")

-- не смотря на то, структура встроена, у нас есть возможность вызвать перекрытый методы через "полный селектор"

Методы для других типов данных

Методы могут быть не только у структур, например для слайса:

type MySlice []int

func (sl *MySlice) add(val int) {
	*sl = append(*sl, val) // разыменовываем указатель
}

func (sl *MySlice) count() int {
	return len(*sl)
}

func main() {
	list := MySlice([]int{1, 2})
	fmt.Println(list) // [1 2]
	list.add(5)
	fmt.Println(list, list.count()) //[1 2 5] 3
}

-- тут мы добавили методы добавляения элементов в и подсчета длины, используя в т.ч.разыменование указателей.

Видео-материалы

Key Words for FKN + antitotal forum (CS VSU):