Интерфейсы в 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 отличаются совершенно особым поведением в сравнении со своими собратьями из других языков программирования. Надеюсь, что статья вам понравилась.
Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming