На этом шаге рассмотрим добавление дополнительных методов в Go.
Метод – это особого рода функция, которая вызывается относительно значения пользовательского типа, которое (обычно) передается вызываемому методу. Значение передается по указателю или по значению, в зависимости от того, как объявлен метод. Синтаксис определения методов практически идентичен синтаксису определения функций, за исключением того, что между ключевым словом func и именем метода в круглых скобках указывается приемник (receiver) либо как имя типа, которому принадлежит метод, либо как имя переменной с типом. При вызове метода переменной-приемнику (если указана) автоматически присваивается значение или указатель на значение, относительно которого был произведен вызов метода.
В любой пользовательский тип можно добавить один или более методов. Приемником метода всегда будет значение типа или указатель на значение типа. Однако имена всех методов должны быть уникальными в пределах типа.
Из требования к уникальности имени следует, что нельзя создать два метода с одинаковыми именами, один из которых принимает значение, а другой – указатель на значение.
Еще одно следствие – отсутствие поддержки перегруженных методов , то есть методов с одинаковыми именами и разными сигнатурами. Один из способов реализовать подобие перегруженных методов заключается в создании методов с переменным числом аргументов, однако в языке Go принято использовать функции с уникальными именами. Например, тип strings.Reader предоставляет три разных метода чтения: strings.Reader.Read(), strings.Reader.ReadByte() и strings.Reader.ReadRune().
type Count int func (count *Count) Increment() { *count++ } func (count *Count) Decrement() { *count-- } func (count Count) IsZero() bool { return count == 0 }
Это простой пользовательский тип, основанный на встроенном типе int, поддерживает три метода, из которых первые два принимают указатель на приемник, поскольку изменяют значение, относительно которого вызываются.
var count Count i := int(count) count.Increment() j := int(count) count.Decrement() k := int(count) fmt.Println(count, i, j, k, count.IsZero())
Данный фрагмент демонстрирует практическое применение типа Count.
Архив с примерами можно взять здесь.
Рассмотрим чуть более сложный пользовательский тип, на этот раз основанный на структуре.
type Part struct { Id int // Именованное поле (агрегирование) Name string // Именованное поле (агрегирование) } func (part *Part) LowerCase() { part.Name = strings.ToLower(part.Name) } func (part *Part) UpperCase() { part.Name = strings.ToUpper(part.Name) } func (part Part) String() string { return fmt.Sprintf("%d %q", part.Id, part.Name) } func (part Part) HasPrefix(prefix string) bool { return strings.HasPrefix(part.Name, prefix) }
Методы, принимающие значение вместо указателя, не могут изменить значение, к которому применяются.
part := Part{5, "программирование"} part.UpperCase() part.Id += 11 fmt.Println(part, part.HasPrefix("п"))
Когда пользовательские типы основаны на структурах, их значения можно создавать, указывая имя типа с фигурными скобками за ним, содержащими список начальных значений полей.
После создания значения part его можно использовать для вызова методов (например, Part.UpperCase()), обращаться к его экспортируемым (общедоступным) полям (например, Part.Id) и выводить его содержимое, полагаясь на функции вывода в языке Go, всегда использующие метод String() типа, если он имеется.
Архив с примерами можно взять здесь.
На следующем шаге рассмотрим понятие множества методов типа в Go.