Сравните method value и method expression в Go: когда фиксируется получатель метода и где он передаётся при вызове?
Method value сохраняет получателя в момент получения значения метода, поэтому при последующем вызове получатель передаётся неявно. Method expression получателя не сохраняет: она возвращает функцию, в которой получатель становится первым явным аргументом и выбирается при каждом вызове.
В Go методы должны оставаться совместимыми с общей моделью функций первого класса: их можно передавать как значения, сохранять и вызывать позже. Поэтому язык предоставляет два варианта — удобный связанный с объектом method value и более явный method expression.
Такое разделение позволяет выбирать между захватом конкретного получателя и передачей получателя отдельно. Это особенно полезно при работе с интерфейсами, обработчиками и обобщёнными алгоритмами вызова.
Если разработчик перепутает эти формы, он может ошибочно ожидать, что сохранённый метод начнёт работать с новым значением переменной-получателя. В результате обработчик будет продолжать обращаться к исходному получателю, хотя внешняя переменная уже указывает на другой объект.
Обратная ошибка тоже опасна: method expression требует явно передать получателя при каждом вызове. Это делает вызов менее компактным, но позволяет выбирать конкретное значение динамически и не создавать отдельное связанное значение метода.
При обращении к методу через значение, например к p.Print, Go немедленно вычисляет получателя и создаёт method value. Для интерфейса сохраняется копия интерфейсного значения: его динамический тип и динамическое значение. Последующая переназначенная переменная p уже не изменит сохранённый method value.
Method expression записывается через тип или интерфейс, например Printer.Print. Она имеет обычную функцию с дополнительным первым параметром-получателем. Для интерфейса этим параметром является значение интерфейса, поэтому фактическая реализация определяется динамическим типом переданного значения во время вызова.
bound имеет форму функции без аргумента-получателя: получатель уже сохранён. expr имеет форму функции с первым аргументом типа Printer; при её вызове интерфейсная диспетчеризация выбирает реализацию Print динамического типа.
Method value удобен, когда получатель неизменен и функцию нужно передать в callback или горутину. Method expression лучше подходит для универсального кода, где получатель должен выбираться отдельно для каждого вызова; цена этой гибкости — явный аргумент и более подробный синтаксис.
Сервис формирует список обработчиков сообщений. Один вариант — сохранить handler.Handle для конкретного обработчика. Это упрощает вызов и снижает вероятность передать неправильный объект, но обработчик фиксируется в момент создания списка.
Другой вариант — сохранить Handler.Handle как method expression и передавать конкретный Handler при обработке каждого сообщения. Такой подход позволяет использовать один набор функций для разных обработчиков, но требует явно контролировать передаваемый получатель.
Если обработчики не меняются после построения списка, выбран method value: он проще и явно связан с нужным объектом. Если набор объектов выбирается динамически, выбран method expression, поскольку получатель должен определяться в момент обработки; результатом становится переиспользуемая функция без скрытого захвата конкретного экземпляра.
Нет. Получатель вычисляется при создании method value. Если переменную интерфейсного типа переназначить на другой динамический объект, ранее сохранённый method value продолжит обращаться к исходному получателю.
При этом сохраняется значение получателя по обычным правилам Go: для указателя копируется указатель, а не объект; изменение объекта через этот указатель будет видно. Для значения-структуры сохраняется копия самой структуры.
Да. Если method expression получена от интерфейса, её первый аргумент имеет интерфейсный тип. При передаче конкретного значения Go использует динамический тип этого интерфейса и вызывает соответствующую реализацию метода.
Это отличается не от method value по выбору реализации, а от способа передачи получателя: method value уже хранит конкретное интерфейсное значение, а method expression получает его при вызове.
Нужно соблюдать методный набор типа. Если метод объявлен для *T, корректной формой method expression является (*T).Method; форма (T).Method не становится автоматически допустимой только потому, что отдельный вызов метода на адресуемом значении T может неявно взять его адрес.
Поэтому method expression строже отражает методный набор: указательный получатель должен быть передан как *T. Это предотвращает неявное изменение формы функции и делает её первый аргумент однозначным.