> For the complete documentation index, see [llms.txt](https://docs.golang.org.ua/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.golang.org.ua/metody.md).

# Методы

Начиная беседу о методах, давайте сперва - извините за "масло масляное" - определимся в определениях. Неспециализированный словарь общего назначения даст нам такую дефиницию: "*метод есть способ теоретического исследования или практического осуществления чего-либо*". Что ж, посмотрим, как обстоит дело в компьютерных наших науках. А дело обстоит достаточно зыбко, поскольку, от языка к языку, терминология разнится. Конкретнее, происходит некоторое взаимопереплетение понятий "метод" и "функция". Во многих (как правило, процедурного класса) языках программирования вовсе отсутствует понятие методов: это - царство функций. В таком языке, как JavaScript, методом могут назвать функцию, являющуюся свойством (иначе - полем) переменной типа object (внимание, первые звоночки ООП!). В собственно "чистокровных" (thoroughbred) объектно-ориентированных языках, каковыми являются, к примеру, C# и Java, есть методы как члены класса, глубоко связанные с самим понятием класса сущностно, но отдельно стоящие функции отсутствуют вовсе. Впрочем, в разговорной практике слова "метод" и "функция" часто не дифференцируются, и любое образование, имеющее "функциональную" сигнатуру, и, в особенности, имеющее отношение к некоему API, с равным успехом именуется методом или функцией на усмотрение говорящего. Но что ожидает нас в языке Go?

## Методы в языке Go

Методы в языке Go представляют собою функции особого типа. Их использование ассоциировано сугубо со структурами и именованными типами и, наряду с так называемыми интерфейсами, представляет собой основу ООП в этом языке. Итак, метод - это функция, связанная с определённым типом (исключая базовые). Внешне, методы в Go напоминают мне чем-то методы расширения в C#. В действительности, мы с вами с момента изначального знакомства с языком Go уже полноценно пользуемся методами. Например, вездесущий **fmt.Println()** - есть ни что иное, как экспортируемый метод, определённый в пакете **fmt**.

## Синтаксис

Что нам понадобится для создания метода? Во-первых, нужно продекларировать именованный тип или структуру, с которыми наш метод будет связан. Здесь я хочу подчеркнуть, что, не смотря на то, что мы уже уверенно ступили на дорогу ООП, методы в действительности могут быть довольно разнообразными по своей сути; воплощение какой концепции вы получите на выходе - вам решать. Как говаривал дедушка Фрейд, иногда сигара - это просто сигара. Поэтому начнём с простого, как бы издалека.

Но вернёмся на землю, то есть к, собственно, синтаксису. Итак, допустим, некий "подопытный" тип создан. Что дальше? Всё просто: синтаксически  метод - та же функция, при декларации которой между ключевым словом **func** и именем функции мы добавляем «получателя» - наш ассоциированный тип. Вызов метода производится при помощи оператора "точка". Знакомая конструкция, не правда ли? Проиллюстрируем сказанное простыми примерами, не претендующими пока на явную  причастность к ООП.

```go
package main

import "fmt"

type T uint // простой именованный тип

//метод
func (t T) factorial() {
	result := 1
	for i := 2; i <= int(t); i++ {//обратите внимание на приведение типа
		result *= i
	}
	fmt.Println(result)
}

func main() {

	var t T = 5

	t.factorial()// вызов метода через оператор "."
}
//Результат:
//120
```

&#x20;Как видите, здесь мы просто реализовали удобную математическую функцию, не претендуя в данном случае на глубокое воплощение принципов ООП. Обратите внимание, нам пришлось в данном случае отказаться от использования рекурсивного алгоритма. Отметьте для себя этот важный момент: рекурсия - неподходящий способ для методов, поскольку метод не может вызвать сам себя, но привязан лишь к ассоциированному с ним типу (структуре).

Интереса ради, расширим наш математический пример. Заставим нашу дрессированную программу разучить ещё один фокус: пускай это будет n-ное число ряда Фибоначчи:

```go
package main

import "fmt"

type T uint // простой именованный тип

//метод factorial
func (t T) factorial() {
	result := 1
	for i := 2; i <= int(t); i++ { //обратите внимание на приведение типа
		result *= i
	}
	fmt.Println(result)
}

//метод fibonacci
func (t T) fibonacci() {
	n := int(t)

	var (
		a  int = 1
		ta int
		b  int = 1
		tb int
		c  int = 1
		rc int = 0
		tc int
		d  int = 0
		rd int = 1
	)
	
	for n != 0 {

		if n%2 != 0 { // Если степень нечетная

			// Умножаем вектор R на матрицу A
			tc = rc
			rc = rc*a + rd*c
			rd = tc*b + rd*d
		}

		// Умножаем матрицу A на саму себя
		ta = a
		tb = b
		tc = c
		a = a*a + b*c
		b = ta*b + b*d
		c = c*ta + d*c
		d = tc*tb + d*d

		n >>= 1 // Уменьшаем степень вдвое

	}	
	fmt.Println(rc)
}

func main() {

	var t T = 7

	t.factorial()
	t.fibonacci()
}
//Результат:
//5040
//13	
```

А что, мне уже нравится! Почему бы нет: вырисовывается некоторое подобие калькулятора... К слову, обратите внимание на избранную нами реализацию алгоритма вычисления чисел ряда Фибоначчи: это - самое эффективное решение данной задачи с помощью быстрого возведения матрицы в степень. Nota bene, sapienti sat...

Дополню изложение ещё одним примечательным примером. Рискнём, к тому же, добавить сюда гомеопатическую дозу ООП. Отчего бы нам не поработать со срезом? Как вы, вероятно, помните, благодаря возможности присвоения кортежем, существует хорошая практика работы со срезом с экономией памяти; как это называют, "на месте". Особенно полезной часто бывает реализация реверса. Сделаем же это, обличив в форму метода! Некто любознательный может поинтересоваться: в чём смысл? Поясню. Временами приходится работать с последовательностями байтов, имеющую различный так называемый **endianness** (порядок следования), коих, как известно, существует два: **big endian** и **little endian**. И, что самое весёлое, в иных протоколах обмена данными, может встречаться прелюбопытный микс из обоих "ордеров"! Конвертация неизбежна. Приступим.

```go
package main

import "fmt"

type T []byte

func (t T) reverse() {
	for i, j := 0, len(t)-1; i < j; i, j = i+1, j-1 {
		t[i], t[j] = t[j], t[i]
	}
}

func (t T) String() string {
	s := ""
	for _, val := range t {
		s += fmt.Sprintf("%#02x ", val)
	}
	return s
}

func main() {
	var t T = T{0x01, 0x02, 0x03}
	fmt.Println(t)
	t.reverse()
	fmt.Println(t)
}
//Результат:
//0x01 0x02 0x03 
//0x03 0x02 0x01
```

Вот так, вкусно и полезно. Однако, как было обещано, в данном примере для вас заготовлен небольшой ООП-сюрприз. Почему мы продекларировали метод **String()**, но его явного использования не заметно вовсе? Ошибки не было. Мы смогли использовать напрямую вызов **fmt.Println(t)** вместо ожидаемой конструкции **fmt.Println(t.String())** благодаря тому, что метод **String()** реализует интерфейс **Stringer**, определённый в пакете **fmt.**&#x20;

```go
type Stringer interface {
    String() string
}
```

Как следствие, наш тип **Т,** реализуя интерфейс **Stringer** пакета **fmt**, позволяет нам использовать конструкцию **fmt.Println(t)** напрямую, получая на выходе текст, отформатированный в соответствии с правилами, определёнными в методе **String**. Разумеется, мы несколько забежали вперёд и более детальное обсуждение этой темы у нас ещё впереди. Однако, для оживления изложения, мы немного прикоснулись к ООП в языке Go в разрезе реализации методов стандартных интерфейсов. Запомните: для языка Go это - повсеместная практика. Впрочем, если вы по каким-то причинам не желаете пользоваться подобным функционалом, вы вполне можете оставаться в рамках парадигмы процедурного программирования. Функция **printBuf()** вполне заменит нам ООП - конструкцию:

```go
package main

import (
	"fmt"
)

type T []byte

func (t T) reverse() {
	for i, j := 0, len(t)-1; i < j; i, j = i+1, j-1 {
		t[i], t[j] = t[j], t[i]
	}
}

func printBuf(buffer []byte) {
	for _, val := range buffer {
		fmt.Printf("%#02x ", val)
	}
	fmt.Println()
}

func main() {
	var t T = T{0x01, 0x02, 0x03}
	printBuf([]byte(t))
	t.reverse()
	printBuf([]byte(t))
}
//Результат (он аналогичен):
//0x01 0x02 0x03 
//0x03 0x02 0x01
```

Выбор расстановки акцента на ООП или процедурном стиле - зачастую дело вашего личного вкуса; это также пребывает в зависимости от неких соглашений, принятых в той команде, где вы работаете. В отличие от "чистых" ООП языков, Go не принуждает вас явно к тому или иному выбору.

Далее, небесполезно будет знать, что Go допускает бесконфликтное сосуществование одноимённых функции и метода в одной области видимости. И ещё, поскольку метод привязан к своему типу и как бы входит в его пространство имён, вполне корректным приёмом является объявление одноимённых методов для различных типов, что, имея в виду понятие реализации интерфейсов, является одной из форм претворения принципов ООП. Согласитесь, выделение, к примеру, метода **area()** выглядит логично для любой структуры, описывающей некую геометрическую фигуру на плоскости:

```go
package main

import (
	"fmt"
	"math"
)

//структуры - фигуры
type circle struct{ radius float64 }
type rectangle struct{ a, b float64 }
type triangle struct{ a, b, c float64 }

//одноименные методы, разные типы-получатели
func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (r rectangle) area() float64 {
	return r.a * r.b
}

//площадь треугольника по формуле Герона
func (t triangle) area() float64 {
	var p float64 = (t.a + t.b + t.c) / 2
	return math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))
}

func main() {
	c := circle{3}
	r := rectangle{2, 5}
	t := triangle{4, 5, 6}

	fmt.Println(c.area())
	fmt.Println(r.area())
	fmt.Println(t.area())
}
//Результат:
//28.274333882308138
//10
//9.921567416492215
```

## Импорт пакетов и методы

Как известно, одна из основных практик языка Go - импортирование пакетов. То есть использование кода, написанного другими программистами, либо же повторное использование собственного кода, во исполнение принципа **DRY** - don't repeat yourself. Результатом этого является ситуация, когда в ваш код регулярно проникают "посторонние" типы. Плохой новостью является тот факт, что для типа, объявленного в другом пакете (и авторство этого пакета совершенно не важно), вы не можете создавать методы. Приведём простой пример. Некогда, в главке [Алфавит и пакеты](/alphabet-and-packages.md) мы создали незамысловатый "учебный" пакет **fqdn.org/common-utils** (вспомните, как это было). Не мудрствуя лукаво, добавим в этот пакет новый файл **objects.go**, дабы было ясно, что его предназначение - хранить именованные типы данного пакета:

```go
package utils

//AwesomeType is just a simple named type
//(не забываем корректно комментировать экспортируемый тип!)
type AwesomeType int
```

Далее, создадим собственно файл программы **main.go**, импортируем в код вышеописанный пакет **fqdn.org/common-utils** и попытаемся создать метод для типа **utils.AwesomeType**. поскольку базовый тип - int, пускай это будет, допустим, проверка значения на чётность:

```go
package main

import (
	"fmt"
	"fqdn.org/common-utils"
)

//проверим на чётность
func (a utils.AwesomeType) isEven() bool {
	if a%2 == 0 {
		return true
	}
	return false
}

func main() {
	var a utils.AwesomeType = 4
	fmt.Println(a.isEven())
}
//Результат:
//.\main.go:9:6: cannot define new methods on non-local type utils.AwesomeType
//.\main.go:18:15: a.isEven undefined (type utils.AwesomeType has no field or method isEven)
```

Результатом будет ошибка компиляции: мы не можем определять новые методы для нелокального типа, компилятор не велит! На самом деле, это очень распространённая ситуация, и раз так, необходимо решение! Решением является использование обёрток-wrapper\`ов. Нужно тем или иным образом привязать требуемый внешний тип к локальному - либо методом прямого выведения, либо путём инкапсуляции в качестве поля в локальную структуру:

```go
//Делаем так:
type T utils.AwesomeType

//Или вот так:
type S struct {
    a utils.AwesomeType
    // possible other fields
    //...
    //
}
```

Как результат, мы можем определить нужный метод, который, пусть и косвенно, будет ассоциироваться с нелокальным типом. Это работает:

```go
package main

import (
	"fmt"
	"fqdn.org/common-utils"
)

type T utils.AwesomeType

//проверим на чётность
func (t T) isEven() bool {
	if t%2 == 0 {
		return true
	}
	return false
}

func main() {
	var t T = 4
	fmt.Println(t.isEven())
}
//Результат:
//true
```

Главное в данном случае - это понять принцип. На практике это позволит впоследствии строить действительно сложные конструкции, при необходимости.&#x20;

## Методы и указатели

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