Статьи

Более пристальный взгляд на интерфейсы Go

Вы когда-нибудь слышали о «Лучший интерфейс — нет интерфейса»? Хотя это может быть правдой для дизайна продукта, конечно, это не так, когда речь идет о программировании на Go. Наоборот, вероятно, лучше придерживаться принципа «ошибка на стороне слишком большого количества интерфейсов». Почему? В этой статье мы рассмотрим, что такое интерфейсы, как они реализованы в Go и почему они так полезны. Давайте погрузимся прямо в!

Что такое интерфейсы?

Вообще говоря, интерфейс определяет поведение объекта и, следовательно, как этот объект может использоваться другими частями системы. Интерфейс описывает, что может сделать объект, но не то, как объект фактически выполняет инструкции. Как следствие, реализация объекта может быть заменена без нарушения функциональности для потребителя.

В таких языках, как PHP или Java, интерфейсы указывают, какие методы должен реализовывать класс, и в свою очередь каждый класс объявляет, какие интерфейсы он реализует. Многие классы могут реализовывать один и тот же интерфейс, и тогда система может работать на интерфейсах вместо реализации.

Интерфейсы в Go

В Go интерфейсы имеют ту же цель, но есть несколько отличий от традиционных языков, которые делают интерфейсы намного более полезными и универсальными. Итак, что такое интерфейсы в Go? Во-первых, интерфейс в Go просто определяется как набор методов . Например, интерфейс Reader из стандартной библиотеки выглядит так:

[Golang]
type Reader interface {
Чтение (p [] byte) (n int, ошибка err)
}
[/ Golang]

Он определяет один метод, Read[]byteinterror Поначалу интерфейс, содержащий только один метод, может показаться вам необычным, но на самом деле это не редкость в Go. Go полон красивых, маленьких, сочетающихся частей — мы вернемся к этому позже. А сейчас давайте еще раз посмотрим на код. Мы замечаем, что имя интерфейса — это просто имя метода Read-er Например, у нас также есть интерфейс WriterPrinterWritePrint

Второй важный момент, который нужно знать об интерфейсах в Go, заключается в том, что они вводят новый тип . В приведенном выше примере мы объявляем новый тип Readerinterface Значением типа интерфейса может быть все, что удовлетворяет интерфейсу.

Так как типы реализуют интерфейс? Традиционно типы объявляют о своем намерении реализовать интерфейс, например, используя ключевое слово implements В Go интерфейсы выполняются неявно . Тип реализует интерфейс, если он реализует все методы набора. Итак, все, что является ReaderRead(p []byte) (n int, err error) Например, тип Buffer из стандартной библиотеки можно использовать как ReaderRead

Все еще со мной? Хорошо! Давайте посмотрим, как все это делает интерфейсы в Go настолько мощными и для чего их можно использовать.

Пустой интерфейс

Я упоминал, что интерфейсы — это наборы методов. Помните свои уроки по математике с давних времен? Вы, наверное, узнали, что наименьший набор — это пустой набор. Итак, в Go самый маленький интерфейс — это пустой интерфейс , который отмечен как interface{} Этот интерфейс реализуется каждым типом в Go, потому что у каждого типа нет по крайней мере методов (и интерфейсы выполняются неявно!). Хотя это может быть логичным, для чего должен быть хорош интерфейс без методов? Итак, поскольку интерфейс также вводит новый тип, у нас теперь есть тип, который можно использовать в местах, где мы обрабатываем значения неизвестного типа. Это несколько похоже на «типизацию утки» в динамических языках. Вероятно, это хорошая идея — избегать пустого интерфейса в Go, насколько это возможно, но очень полезно иметь доступ к нему, когда вам это нужно. Например, функция Printfmtinterface{}

[Golang]
func Print (a… interface {}) (n int, error error) {

}
[/ Golang]

Это имеет смысл, поскольку вы можете печатать stringintPrint Внутренне Print

Композитные интерфейсы

Хотите еще теории множеств? Помните, что набор также может содержать другие наборы. Внешний набор состоит из всех элементов внутренних наборов. И конечно же, это возможно и в Go. В стандартной библиотеке есть даже выдающийся пример: интерфейс ReadWriter . ReadWriterReaderWriter Это объявлено так:

[Golang]
Тип интерфейса ReadWriter {
читатель
писатель
}
[/ Golang]

Это означает, что все, что реализует ReaderWriterReadWriter Помните, я говорил о маленьких красивых кусочках Го? Это одна из них.

Что могут реализовывать интерфейсы?

Пока что мы знаем, что все, что реализует набор методов интерфейса, будет удовлетворять интерфейсу. Теперь, в отличие, например, от PHP или Java, к чему-либо в Go могут быть прикреплены методы. Это, в свою очередь, означает, что почти все может удовлетворить интерфейс. Например:

[Golang]
Тип Счетчик int
func (счетчик * Счетчик) Чтение (p [] байт) (n int, ошибка err) {

}
[/ Golang]

Мы определяем новый тип Counterint Затем мы присоединяем метод ReadCounterReader

Использование стандартной библиотеки

Скажем, теперь вы хотите распечатать счетчик. Конечно, вы можете написать метод, который сделает это за вас, но удобнее взглянуть на пакет fmtPrint . Оказывается, он может печатать значения типа интерфейса Stringer По соглашению, StringerString Итак, если вы добавите метод StringStringer Затем вы можете передать его в fmt.PrintString

На самом деле, вы могли бы передать Counterfmt.PrintintString

[Golang]
func (counter * Counter) String () строка {
вернуть «Значение моего счетчика:» + strconv.Itoa (counter)
}
[/ Golang]

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

Вывод

Мы видели, что интерфейсы Go — очень мощная концепция. В то же время они чувствуют себя намного легче, чем в других языках, главным образом потому, что они неявно удовлетворены. Как следствие, интерфейсы создают одну из центральных частей при проектировании системы на Go. Ошибка на стороне слишком многих из них!