Полиморфизм с интерфейсами в Golang
Интерфейсы в 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 представлены «типами»
Интерфейсы можно использовать как «типы» в 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
.
Ссылки
Last updated
Was this helpful?