Интерфейсы в Golang работают в совершенно особенной манере в сравнении с интерфейсами на других языках серверного программирования. Прежде чем углубляться в тему, начнём с базовых понятий. Интерфейсы в Golang предоставляют список сигнатур функций, которые реализовываются любой «структурой» для работы с конкретными интерфейсами. Давайте разберёмся, что под этим подразумевается. Для тех, кто только знакомится с Golang, возможно, будет полезно почитать сначала эти статьи:
- Конкурентность и параллелизм в Golang. Горутины.
- Обработка ошибок в Golang с помощью Panic, Defer и Recover
- Введение в каналы 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 представлены «типами»
Интерфейсы можно использовать как «типы» в 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
.
Интерфейсы в Golang отличаются своеобразием…
Эта особенность заметно выделяет его по реализации интерфейса на фоне других серверных языков. Главные выводы, которые можно сделать на основе этого кода:
- В Golang интерфейсы можно использовать как типы.
- Объект, имеющий все интерфейсные функции, можно присвоить переменной интерфейса.
Давайте напишем весь сценарий
// Создание простого интерфейса 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 отличаются совершенно особым поведением в сравнении со своими собратьями из других языков программирования. Надеюсь, что статья вам понравилась.
Читайте также:
- Объектно-ориентированное программирование в Golang
- Создание интерфейсов RESTful API в Golang
- Идеальная настройка вашего Golang проекта
Перевод статьи: Mayank Gupta: Polymorphism with Golang Interfaces