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


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


    Перевод статьи


    Поделиться статьей:


    Вернуться к статьям

    Комментарии

      Ничего не найдено.