Интерфейсы в Golang работают в совершенно особенной манере в сравнении с интерфейсами на других языках серверного программирования. Прежде чем углубляться в тему, начнём с базовых понятий. Интерфейсы в Golang предоставляют список сигнатур функций, которые реализовываются любой «структурой» для работы с конкретными интерфейсами. Давайте разберёмся, что под этим подразумевается. Для тех, кто только знакомится с 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, то есть мы можем создать переменные типа Interface
и любая структура, выполняющая контракт на функцию, может быть присвоена этой переменной Interface
.
Так же как Manager
, объявляя любые типы, содержащие все функциональные требования к контракту интерфейса Employee
, мы сможем создать их объекты и присвоить их переменной Interface
. Вот пример:
newManager := Manager{Name: "Mayank", Age: 30, Designation: "Developer" Salary: 10}
var employeeInterface Employee
employeeInterface = newManager
employeeInterface.GetDetails()
Стоит сделать несколько замечаний:
Manager
.Manager
содержит все функции, требующиеся для интерфейса Employee
.Employee
.Manager
присвоен переменной employeeInterface
.employeeInterface
теперь может использоваться для вызова функций, принадлежащих интерфейсу типа Employee
.Здесь мы создали переменную интерфейсного типа, и любая структура, содержащая все функции, указанные в контракте интерфейса, может быть ей присвоена. Следовательно, мы могли присвоить объект типа Manager
интерфейсу типа Employee
.
Эта особенность заметно выделяет его по реализации интерфейса на фоне других серверных языков. Главные выводы, которые можно сделать на основе этого кода:
Давайте напишем весь сценарий
// Создание простого интерфейса 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()
То, что мы можем создавать переменную типа 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 отличаются совершенно особым поведением в сравнении со своими собратьями из других языков программирования. Надеюсь, что статья вам понравилась.
Перевод статьи
Комментарии