Одним из самых непонятных моментов в изучении Go для меня стало понятие интерфейса. Книга по Go Донована-Керниган, которая лежит у меня на столе, убивает понимание первой же строкой. Цитирую: “Интерфейсные типы выражают обобщения или абстракции поведения других типов”.

Звучит выстрел, мозг новичка стекает по занавеске на батарею отопления.

Нормальное, короткое определение звучит так: “Интерфейсы - это именованные коллекции сигнатур методов”. Понятнее? Всё ещё нет?

Давайте разбираться.

  • Что такое переменная?

Именованная область памяти.

  • Что такое метод?

Функция, описывающая поведение объекта (структуры).

  • Что такое сигнатура функции?

Описание принимаемых для обработки переменных и возвращаемых результатов.

Т.е. интерфейс - это просто описание любых методов любых структур.

Как это выглядит в жизни:

1) Создадим простенький проект foobar, состоящий из двух модулей(packages) - foo и bar. Интерфейсы в Go 1

2) Пусть пакет foo содержит структуры Dog и Cat. Интерфейсы в Go 2

Обратим внимание, что и Dog и Cat содержат одинаковый метод Say(). Или, иначе говоря, сигнатуры методов этих структур совпадают.

3) А пакет bar содержит интерфейс Sayer, в котором описан метод Say(). Интерфейсы в Go 3

Говоря книжным языком - методы Say() структур Dog и Cat удовлетворяют интерфейсу Sayer. При этом, обратите внимание, пакет bar ничего не знает о пакете foo. Интерфейс - это абстрактный список, поэтому bar не импортирует те пакеты, в которых Say() присутствует.

4) Идём теперь в функцию main. Интерфейсы в Go 4

Как мы разбирались выше, интерфейс - это именованная коллекция. Это значит, что нам надо создать переменную типа интерфейс. Что мы и делаем, объявив переменную s типа bar.Sayer. Наша абстракция готова обрести плоть, но пока память под неё не выделена - попытка распечатать переменную s отрисует nil.

5) А дальше, как в случае с обычными переменными, мы присваиваем интерфейсу значение - структуру. Интерфейсы в Go 5

Если попробовать распечатать s, мы увидим, что теперь s содержит ссылку - &{что-то}. Это что-то и есть наша структура. В любой момент мы можем присвоить s другую.И наш интерфейс сможет использовать объявленные в текущей присвоенной структуре методы.

6) В нашем случае s.Say() ожидаемо выведет “Bark!Bark!”. Интерфейсы в Go 6

7) Если теперь взять и присвоить интерфейсной переменной другую структуру(объект) - получим возможность использовать метод Say() этой новой структуры. Интерфейсы в Go 7

Таким образом, прогоняя через интерфейс, объявленный где нам нужно, те или иные структуры(объекты), получаем возможность использовать их методы. Что даёт нам высокий уровень абстракции в коде.

Важно понимать, что поля структуры, “зацепленной” за интерфейс, недоступны напрямую. Но всегда можно определить метод, который вернёт нам какие-то данные из лежащего “за” интерфейсом объекта. Этим достигается инкапсуляция данных - мы работаем только с поведением, но не с данными структуры напрямую.

8) Что же это всё означает в реальной жизни? Интерфейсы в Go 8 А в реальной жизни можно, например, полностью отделить работу с базой данных от остального кода. Объявив в main (или где нужно) интерфейс (Connector на скриншоте), мы можем использовать метод Read.

И если мы внезапно решим сменить какую-нибудь Postgresql DB на какую-нибудь Mongo DB, достаточно будет лишь поменять пакет db и объявить в нём соответствующий интерфейс. Остальной наш код совершенно не поломается.