Интерфейсы в Golang работают в совершенно особенной манере в сравнении с интерфейсами на других языках серверного программирования. Прежде чем углубляться в тему, начнём с базовых понятий. Интерфейсы в Golang предоставляют список сигнатур функций, которые реализовываются любой «структурой» для работы с конкретными интерфейсами. Давайте разберёмся, что под этим подразумевается. Для тех, кто только знакомится с Golang, возможно, будет полезно почитать сначала эти статьи:

Особенности интерфейсов:

  1. Интерфейсы определяют контракт функции.
  2. Интерфейсы в Golang представлены типами.

Давайте определим простой интерфейс для сотрудника employee

type Employee interface {
  GetDetails() string
  GetEmployeeSalary() int
}

Здесь к интерфейсу Employee мы добавляем две функции — GetDetails и GetEmployeeSalary. Любая структура или тип, которым нужно работать с интерфейсом Employee, должна содержать эти функции, указанные как контракт интерфейса. Так используются преимущества интерфейса.

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

type Manager struct {
  Name string
  Age int
  Designation string 
  Salary int
}

func (mgr Manager) GetDetails() string {
  return mgr.Name + " " + mgr.Age;
}

func (mgr Manager) GetEmployeeSalary int {
  return mgr.Salary
}

Здесь у нас определён тип, который выполняет контракт, указанный интерфейсом Employee. Давайте посмотрим, какие преимущества предлагает интерфейс при работе с этими вновь определёнными типами.

Интерфейсы в Golang представлены «типами»

Интерфейсы можно использовать как «типы» в Golang, то есть мы можем создать переменные типа Interface и любая структура, выполняющая контракт на функцию, может быть присвоена этой переменной Interface.

Так же как Manager, объявляя любые типы, содержащие все функциональные требования к контракту интерфейса Employee, мы сможем создать их объекты и присвоить их переменной Interface. Вот пример:

newManager := Manager{Name: "Mayank", Age: 30, Designation: "Developer" Salary: 10}

var employeeInterface Employee

employeeInterface = newManager

employeeInterface.GetDetails()

Стоит сделать несколько замечаний:

  1. Мы создали объект для структуры типа Manager.
  2. Структура Manager содержит все функции, требующиеся для интерфейса Employee.
  3. Создана переменная типа Employee.
  4. Объект Manager присвоен переменной employeeInterface.
  5. employeeInterface теперь может использоваться для вызова функций, принадлежащих интерфейсу типа Employee.

Здесь мы создали переменную интерфейсного типа, и любая структура, содержащая все функции, указанные в контракте интерфейса, может быть ей присвоена. Следовательно, мы могли присвоить объект типа Manager интерфейсу типа Employee.

Интерфейсы в Golang отличаются своеобразием…

Эта особенность заметно выделяет его по реализации интерфейса на фоне других серверных языков. Главные выводы, которые можно сделать на основе этого кода:

  1. В Golang интерфейсы можно использовать как типы.
  2. Объект, имеющий все интерфейсные функции, можно присвоить переменной интерфейса.

Давайте напишем весь сценарий

// Создание простого интерфейса Employee
type Employee interface {
  GetDetails() string
  GetEmployeeSalary() int
}

// Создание нового типа Manager, который содержит все функции, требующиеся интерфейсу Employee
type Manager struct {
  Name string
  Age int
  Designation string 
  Salary int
}

func (mgr Manager) GetDetails() string {
  return mgr.Name + " " + mgr.Age;
}

func (mgr Manager) GetEmployeeSalary int {
  return mgr.Salary
}

// Создание нового объекта типа Manager
newManager := Manager{Name: "Mayank", Age: 30, Designation: "Developer" Salary: 10}

// Создана новая переменная типа Employee
var employeeInterface Employee

// Объект Manager присвоен интерфейсному типу, потому что контракт интерфейса выполнен
employeeInterface = newManager

// Вызов функций, принадлежащих интерфейсу Employee
employeeInterface.GetDetails()

Полиморфизм с интерфейсами Golang

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

Давайте сделаем реализацию другого типа Struct со всеми функциями, требующимися интерфейсу Employee. Создадим новый тип Lead, реализующий эти функции. Он содержит все функции, указанные в контракте интерфейса. Значит, можно присвоить объект переменным интерфейса.

type Employee interface {
  GetDetails() string
  GetEmployeeSalary() int
}

// Создание нового типа Manager, который содержит все функции, требующиеся интерфейсу Employee
type Manager struct {
  Name string
  Age int
  Designation string 
  Salary int
}

func (mgr Manager) GetDetails() string {
  return mgr.Name + " " + mgr.Age;
}

func (mgr Manager) GetEmployeeSalary int {
  return mgr.Salary
}

// Создание нового типа Lead, который содержит все функции, требующиеся интерфейсу Employee
type Lead struct {
  Name string
  Age int
  TeamSize string 
  Salary int
}

func (ld Lead) GetDetails() string {
  return ld.Name + " " + ld.Age;
}

func (ld Lead) GetEmployeeSalary int {
  return ld.Salary
}

// Создание нового объекта типа Manager
newLead := Lead{Name: "Mayank", Age: 30, TeamSize: "30" Salary: 10}

// Создана новая переменная типа Employee
var employeeInterface Employee

// Объект Manager присвоен интерфейсному типу, потому что контракт интерфейса выполнен
employeeInterface = newManager

// Вызов функций, принадлежащих интерфейсу Employee
employeeInterface.GetDetails()

// Тот же интерфейс может хранить значение объекта Lead
employeeInterface = newLead

// Вызов функций объекта Lead, присвоенного интерфейсу Employee
employeeInterface.GetDetails()

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

Использование интерфейса в качестве параметра функции

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

// Функция, принимающая интерфейс в качестве входного параметра
func GetUserDetails(emp Employee) {
  emp.GetDetails()
}

type Employee interface {
  GetDetails() string
}

// Создание нового типа Manager, который содержит все функции, требующиеся интерфейсу Employee
type Manager struct {
  Name string
  Age int
  Designation string 
  Salary int
}

func (mgr Manager) GetDetails() string {
  return mgr.Name + " " + mgr.Age;
}

// Создание нового типа Lead, который содержит все функции, требующиеся интерфейсу Employee
type Lead struct {
  Name string
  Age int
  TeamSize string 
  Salary int
}

func (ld Lead) GetDetails() string {
  return ld.Name + " " + ld.Age;
}

// Создание нового объекта типа Manager
newLead := Lead{Name: "Mayank", Age: 30, TeamSize: "30" Salary: 10}

// Создана новая переменная типа Employee
var employeeInterface Employee

// Объект Manager присвоен интерфейсному типу, потому что контракт интерфейса выполнен
GetUserDetails(newManager)

// Интерфейс можно использовать для вызова функции Lead или Manager...
GetUserDetails(newLead)

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

Использование интерфейсов как типов в структурах

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

Покажем это на простых примерах:

type Person struct {
  name string
  age string 
  user Employee
}

Здесь структура Person содержит параметр User интерфейсного типа Employee. Попробуем присвоить параметру User различные структуры Lead и Manager.

type Person struct {
  name string
  age string 
  user Employee
}

// Присвоение объекта типа Manager параметру User
newPerson := Person{name: "Mayank", age: "32", Manager{Name: "Mayank", Age: 30, Designation: "Developer" Salary: 10}}

// Присвоение объекта типа Lead параметру User
newPerson := Person{name: "Mayank", age: "32", Lead{Name: "Mayank", Age: 30, TeamSize: "30" Salary: 10}}

Здесь интерфейс используется в качестве типа параметра Struct.

Заключение

Интерфейсы в Golang отличаются совершенно особым поведением в сравнении со своими собратьями из других языков программирования. Надеюсь, что статья вам понравилась.

Читайте также:


Перевод статьи: Mayank Gupta: Polymorphism with Golang Interfaces

Предыдущая статьяО чём не говорят инженеры ПО
Следующая статьяПродвинутые методы и техники списков в Python