Методы
Снова принимаем процедуры
Начиная беседу о методах, давайте сперва - извините за "масло масляное" - определимся в определениях. Неспециализированный словарь общего назначения даст нам такую дефиницию: "метод есть способ теоретического исследования или практического осуществления чего-либо". Что ж, посмотрим, как обстоит дело в компьютерных наших науках. А дело обстоит достаточно зыбко, поскольку, от языка к языку, терминология разнится. Конкретнее, происходит некоторое взаимопереплетение понятий "метод" и "функция". Во многих (как правило, процедурного класса) языках программирования вовсе отсутствует понятие методов: это - царство функций. В таком языке, как JavaScript, методом могут назвать функцию, являющуюся свойством (иначе - полем) переменной типа object (внимание, первые звоночки ООП!). В собственно "чистокровных" (thoroughbred) объектно-ориентированных языках, каковыми являются, к примеру, C# и Java, есть методы как члены класса, глубоко связанные с самим понятием класса сущностно, но отдельно стоящие функции отсутствуют вовсе. Впрочем, в разговорной практике слова "метод" и "функция" часто не дифференцируются, и любое образование, имеющее "функциональную" сигнатуру, и, в особенности, имеющее отношение к некоему API, с равным успехом именуется методом или функцией на усмотрение говорящего. Но что ожидает нас в языке Go?
Методы в языке Go
Методы в языке Go представляют собою функции особого типа. Их использование ассоциировано сугубо со структурами и именованными типами и, наряду с так называемыми интерфейсами, представляет собой основу ООП в этом языке. Итак, метод - это функция, связанная с определённым типом (исключая базовые). Внешне, методы в Go напоминают мне чем-то методы расширения в C#. В действительности, мы с вами с момента изначального знакомства с языком Go уже полноценно пользуемся методами. Например, вездесущий fmt.Println() - есть ни что иное, как экспортируемый метод, определённый в пакете fmt.
Синтаксис
Что нам понадобится для создания метода? Во-первых, нужно продекларировать именованный тип или структуру, с которыми наш метод будет связан. Здесь я хочу подчеркнуть, что, не смотря на то, что мы уже уверенно ступили на дорогу ООП, методы в действительности могут быть довольно разнообразными по своей сути; воплощение какой концепции вы получите на выходе - вам решать. Как говаривал дедушка Фрейд, иногда сигара - это просто сигара. Поэтому начнём с простого, как бы издалека.
Но вернёмся на землю, то есть к, собственно, синтаксису. Итак, допустим, некий "подопытный" тип создан. Что дальше? Всё просто: синтаксически метод - та же функция, при декларации которой между ключевым словом func и именем функции мы добавляем «получателя» - наш ассоциированный тип. Вызов метода производится при помощи оператора "точка". Знакомая конструкция, не правда ли? Проиллюстрируем сказанное простыми примерами, не претендующими пока на явную причастность к ООП.
Как видите, здесь мы просто реализовали удобную математическую функцию, не претендуя в данном случае на глубокое воплощение принципов ООП. Обратите внимание, нам пришлось в данном случае отказаться от использования рекурсивного алгоритма. Отметьте для себя этот важный момент: рекурсия - неподходящий способ для методов, поскольку метод не может вызвать сам себя, но привязан лишь к ассоциированному с ним типу (структуре).
Интереса ради, расширим наш математический пример. Заставим нашу дрессированную программу разучить ещё один фокус: пускай это будет n-ное число ряда Фибоначчи:
А что, мне уже нравится! Почему бы нет: вырисовывается некоторое подобие калькулятора... К слову, обратите внимание на избранную нами реализацию алгоритма вычисления чисел ряда Фибоначчи: это - самое эффективное решение данной задачи с помощью быстрого возведения матрицы в степень. Nota bene, sapienti sat...
Дополню изложение ещё одним примечательным примером. Рискнём, к тому же, добавить сюда гомеопатическую дозу ООП. Отчего бы нам не поработать со срезом? Как вы, вероятно, помните, благодаря возможности присвоения кортежем, существует хорошая практика работы со срезом с экономией памяти; как это называют, "на месте". Особенно полезной часто бывает реализация реверса. Сделаем же это, обличив в форму метода! Некто любознательный может поинтересоваться: в чём смысл? Поясню. Временами приходится работать с последовательностями байтов, имеющую различный так называемый endianness (порядок следования), коих, как известно, существует два: big endian и little endian. И, что самое весёлое, в иных протоколах обмена данными, может встречаться прелюбопытный микс из обоих "ордеров"! Конвертация неизбежна. Приступим.
Вот так, вкусно и полезно. Однако, как было обещано, в данном примере для вас заготовлен небольшой ООП-сюрприз. Почему мы продекларировали метод String(), но его явного использования не заметно вовсе? Ошибки не было. Мы смогли использовать напрямую вызов fmt.Println(t) вместо ожидаемой конструкции fmt.Println(t.String()) благодаря тому, что метод String() реализует интерфейс Stringer, определённый в пакете fmt.
Как следствие, наш тип Т, реализуя интерфейс Stringer пакета fmt, позволяет нам использовать конструкцию fmt.Println(t) напрямую, получая на выходе текст, отформатированный в соответствии с правилами, определёнными в методе String. Разумеется, мы несколько забежали вперёд и более детальное обсуждение этой темы у нас ещё впереди. Однако, для оживления изложения, мы немного прикоснулись к ООП в языке Go в разрезе реализации методов стандартных интерфейсов. Запомните: для языка Go это - повсеместная практика. Впрочем, если вы по каким-то причинам не желаете пользоваться подобным функционалом, вы вполне можете оставаться в рамках парадигмы процедурного программирования. Функция printBuf() вполне заменит нам ООП - конструкцию:
Выбор расстановки акцента на ООП или процедурном стиле - зачастую дело вашего личного вкуса; это также пребывает в зависимости от неких соглашений, принятых в той команде, где вы работаете. В отличие от "чистых" ООП языков, Go не принуждает вас явно к тому или иному выбору.
Далее, небесполезно будет знать, что Go допускает бесконфликтное сосуществование одноимённых функции и метода в одной области видимости. И ещё, поскольку метод привязан к своему типу и как бы входит в его пространство имён, вполне корректным приёмом является объявление одноимённых методов для различных типов, что, имея в виду понятие реализации интерфейсов, является одной из форм претворения принципов ООП. Согласитесь, выделение, к примеру, метода area() выглядит логично для любой структуры, описывающей некую геометрическую фигуру на плоскости:
Импорт пакетов и методы
Далее, создадим собственно файл программы main.go, импортируем в код вышеописанный пакет fqdn.org/common-utils и попытаемся создать метод для типа utils.AwesomeType. поскольку базовый тип - int, пускай это будет, допустим, проверка значения на чётность:
Результатом будет ошибка компиляции: мы не можем определять новые методы для нелокального типа, компилятор не велит! На самом деле, это очень распространённая ситуация, и раз так, необходимо решение! Решением является использование обёрток-wrapper`ов. Нужно тем или иным образом привязать требуемый внешний тип к локальному - либо методом прямого выведения, либо путём инкапсуляции в качестве поля в локальную структуру:
Как результат, мы можем определить нужный метод, который, пусть и косвенно, будет ассоциироваться с нелокальным типом. Это работает:
Главное в данном случае - это понять принцип. На практике это позволит впоследствии строить действительно сложные конструкции, при необходимости.
Методы и указатели
Далее, полагаю, нам необходимо сделать акцент на одном сущностном моменте. Дело в том, что все методы можно очевидно разделить на две категории, а именно - по признаку, является ли получатель метода типом, или же указателем. Да, указатели на именованный тип или структуру могут быть использованы при объявлении метода; собственно, в этом плане метод в определённом смысле повторяет поведение обычной функции.
Last updated
Was this helpful?