Вы когда-нибудь слышали о «Лучший интерфейс — нет интерфейса»? Хотя это может быть правдой для дизайна продукта, конечно, это не так, когда речь идет о программировании на Go. Наоборот, вероятно, лучше придерживаться принципа «ошибка на стороне слишком большого количества интерфейсов». Почему? В этой статье мы рассмотрим, что такое интерфейсы, как они реализованы в Go и почему они так полезны. Давайте погрузимся прямо в!
Что такое интерфейсы?
Вообще говоря, интерфейс определяет поведение объекта и, следовательно, как этот объект может использоваться другими частями системы. Интерфейс описывает, что может сделать объект, но не то, как объект фактически выполняет инструкции. Как следствие, реализация объекта может быть заменена без нарушения функциональности для потребителя.
В таких языках, как PHP или Java, интерфейсы указывают, какие методы должен реализовывать класс, и в свою очередь каждый класс объявляет, какие интерфейсы он реализует. Многие классы могут реализовывать один и тот же интерфейс, и тогда система может работать на интерфейсах вместо реализации.
Интерфейсы в Go
В Go интерфейсы имеют ту же цель, но есть несколько отличий от традиционных языков, которые делают интерфейсы намного более полезными и универсальными. Итак, что такое интерфейсы в Go? Во-первых, интерфейс в Go просто определяется как набор методов . Например, интерфейс Reader из стандартной библиотеки выглядит так:
[Golang]
type Reader interface {
Чтение (p [] byte) (n int, ошибка err)
}
[/ Golang]
Он определяет один метод, Read
[]byte
int
error
Поначалу интерфейс, содержащий только один метод, может показаться вам необычным, но на самом деле это не редкость в Go. Go полон красивых, маленьких, сочетающихся частей — мы вернемся к этому позже. А сейчас давайте еще раз посмотрим на код. Мы замечаем, что имя интерфейса — это просто имя метода Read
-er
Например, у нас также есть интерфейс Writer
Printer
Write
Print
Второй важный момент, который нужно знать об интерфейсах в Go, заключается в том, что они вводят новый тип . В приведенном выше примере мы объявляем новый тип Reader
interface
Значением типа интерфейса может быть все, что удовлетворяет интерфейсу.
Так как типы реализуют интерфейс? Традиционно типы объявляют о своем намерении реализовать интерфейс, например, используя ключевое слово implements
В Go интерфейсы выполняются неявно . Тип реализует интерфейс, если он реализует все методы набора. Итак, все, что является Reader
Read(p []byte) (n int, err error)
Например, тип Buffer из стандартной библиотеки можно использовать как Reader
Read
Все еще со мной? Хорошо! Давайте посмотрим, как все это делает интерфейсы в Go настолько мощными и для чего их можно использовать.
Пустой интерфейс
Я упоминал, что интерфейсы — это наборы методов. Помните свои уроки по математике с давних времен? Вы, наверное, узнали, что наименьший набор — это пустой набор. Итак, в Go самый маленький интерфейс — это пустой интерфейс , который отмечен как interface{}
Этот интерфейс реализуется каждым типом в Go, потому что у каждого типа нет по крайней мере методов (и интерфейсы выполняются неявно!). Хотя это может быть логичным, для чего должен быть хорош интерфейс без методов? Итак, поскольку интерфейс также вводит новый тип, у нас теперь есть тип, который можно использовать в местах, где мы обрабатываем значения неизвестного типа. Это несколько похоже на «типизацию утки» в динамических языках. Вероятно, это хорошая идея — избегать пустого интерфейса в Go, насколько это возможно, но очень полезно иметь доступ к нему, когда вам это нужно. Например, функция Print
fmt
interface{}
[Golang]
func Print (a… interface {}) (n int, error error) {
…
}
[/ Golang]
Это имеет смысл, поскольку вы можете печатать string
int
Print
Внутренне Print
Композитные интерфейсы
Хотите еще теории множеств? Помните, что набор также может содержать другие наборы. Внешний набор состоит из всех элементов внутренних наборов. И конечно же, это возможно и в Go. В стандартной библиотеке есть даже выдающийся пример: интерфейс ReadWriter . ReadWriter
Reader
Writer
Это объявлено так:
[Golang]
Тип интерфейса ReadWriter {
читатель
писатель
}
[/ Golang]
Это означает, что все, что реализует Reader
Writer
ReadWriter
Помните, я говорил о маленьких красивых кусочках Го? Это одна из них.
Что могут реализовывать интерфейсы?
Пока что мы знаем, что все, что реализует набор методов интерфейса, будет удовлетворять интерфейсу. Теперь, в отличие, например, от PHP или Java, к чему-либо в Go могут быть прикреплены методы. Это, в свою очередь, означает, что почти все может удовлетворить интерфейс. Например:
[Golang]
Тип Счетчик int
func (счетчик * Счетчик) Чтение (p [] байт) (n int, ошибка err) {
…
}
[/ Golang]
Мы определяем новый тип Counter
int
Затем мы присоединяем метод Read
Counter
Reader
Использование стандартной библиотеки
Скажем, теперь вы хотите распечатать счетчик. Конечно, вы можете написать метод, который сделает это за вас, но удобнее взглянуть на пакет fmt
Print . Оказывается, он может печатать значения типа интерфейса Stringer
По соглашению, Stringer
String
Итак, если вы добавите метод String
Stringer
Затем вы можете передать его в fmt.Print
String
На самом деле, вы могли бы передать Counter
fmt.Print
int
String
[Golang]
func (counter * Counter) String () строка {
вернуть «Значение моего счетчика:» + strconv.Itoa (counter)
}
[/ Golang]
Существует много способов использовать стандартную библиотеку путем реализации правильных интерфейсов, и часто для этого требуется всего один метод.
Вывод
Мы видели, что интерфейсы Go — очень мощная концепция. В то же время они чувствуют себя намного легче, чем в других языках, главным образом потому, что они неявно удовлетворены. Как следствие, интерфейсы создают одну из центральных частей при проектировании системы на Go. Ошибка на стороне слишком многих из них!