[Программирование, Проектирование и рефакторинг, Go] Преимущества интерфейсов в GO
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В языке GO интерфейсы отличаются от других языков. Они немного лучше чем в других популярных языках с точки зрения дизайна. В этой статье я постараюсь объяснить почему.
Я расскажу о преимуществах, приведу примеры и рассмотрю некоторые вопросы, которые могут возникнуть при использовании интерфейсов.
В чем особенность интерфейсов в GO?
В большинстве языков вы описываете один интерфейс, и реализуете их в других местах явно, указывая, что вы реализуете именно их. Но в GO это не так. Вам не нужно указывать явно, что вы реализуете его в своей структуре. Если у структуры есть все функции и они имеют одинаковую сигнатуру с интерфейсом, то она уже его реализует.
Какие преимущество дает эта особенность?
Приватный интерфейс
Интерфейсы удобно описывать внутри модуля. Объясню почему это хорошо. Предположим вы пишите пакет. По-хорошему он должен минимально зависеть от других пакетов, и для этого вы отгораживаетесь от них интерфейсом. Это позволит вам тестировать ваш пакет изолированно и в случае необходимости подменить внешнюю библиотеку. Но этот интерфейс может быть приватным, т.е. другие пакеты о нем ничего не знают.
В конкретный момент нужны конкретные методы от какого-то внешнего объекта, и для этого достаточно иметь интерфейс только с этими конкретными методами. Вам не нужно искать подходящие интерфейсы под создаваемый модуль из внешних или общих пакетов.
Рассмотрим пример.
Я пишу пакет, отвечающий за авторизацию пользователя. В нем необходимо обращаться к репозиторию пользователя. Нам нужны только несколько методов, опишем их в интерфейсе:
package auth
import (
"gitlab.com/excercice_detection/backend"
)
type userRepository interface {
FindUserByEmail(email string) (backend.User, error)
AddUser(backend.User) (userID int, err error)
AddToken(userID int, token string) error
TokenExists(userID int, token string) bool
}
// Auth сервис авторизации
type Auth struct {
repository userRepository
logger backend.Logger
}
// NewAuth создает объект авторизации
func NewAuth(repository userRepository, logger backend.Logger) *Auth {
return &Auth{repository, logger}
}
// Autentificate Проверяет существование токена пользователя
func (auth Auth) Autentificate(userID int, token string) bool {
return auth.repository.TokenExists(userID, token)
}
Для примера я показал как используется один из методов, на самом деле они используются все.
В главном методе main создается и используется объект авторизации:
package main
import (
"gitlab.com/excercice_detection/backend/auth"
"gitlab.com/excercice_detection/backend/mysql"
)
func main() {
logger := newLogger()
userRepository := mysql.NewUserRepository(logger)
err := userRepository.Connect()
authService := auth.NewAuth(userRepository, logger)
...
При создании объекта авторизации достаточно передать userRepository, у которого реализованы все методы, которые есть в интерфейсе, а пакет mysql при этом ничего не знает об интерфейсе, описанном в сервисе авторизации. Он и не должен об этом знать. Нет лишних зависимостей. Код остается чистым.
В других языках программирования вам бы пришлось описывать интерфейс в общем модуле. И указывать в классе репозитория, что он реализует этот интерфейс. А в классе авторизации использовать его. Хотя на самом деле об этом интерфейсе достаточно знать только модулю авторизации, потому что он нужен только ему.
Если вы передадите объект, который не реализует нужный интерфейс, вы получите ошибку на этапе компиляции.
Такой интерфейс удобно расширять
Если вам в будущем пригодятся другие методы из репозитория, вы можете просто добавить их в этот приватный интерфейс. Он немного усложниться, но только внутри этого модуля. Вам не нужно докидывать их в неком общем интерфейсе, а затем описывать методы везде, где он реализуется.
Тесты
В тестах тоже удобно использовать этот интерфейс. Достаточно подменить методы, которые будут использоваться в модуле. Не нужно подменять ничего лишнего.
Пример мока:
type userRepositoryMock struct {
user backend.User
findUserErr error
addUserError error
addUserID int
addTokenErr error
tokenExists bool
}
func (repository userRepositoryMock) FindUserByEmail(email string) (backend.User, error) {
return repository.user, repository.findUserErr
}
func (repository userRepositoryMock) AddUser(backend.User) (userID int, err error) {
return repository.addUserID, repository.addUserError
}
func (repository userRepositoryMock) AddToken(userID int, token string) error {
return repository.addTokenErr
}
func (repository userRepositoryMock) TokenExists(userID int, token string) bool {
return repository.tokenExists
}
Далее, в тестах, userRepositoryMock можно подсунуть вместо обычного userRepositorу, подставляя нужные значения, которые должна вернуть функции.
Как понять, кто на самом деле реализует метод, используемый из интерфейса?
Кажется, что закрываясь интерфейсом и не указывая явно кто его реализует, мы теряем знание о том, как на самом деле работает нужная функция. Но, поскольку GO является строго типизированным языком, то узнать кто реализует метод достаточно просто. Например IDE GoLand умеет так делать.
Чтобы убедиться, что структура реализует интерфейс, достаточно запустить компилятор. Он покажет, каких методов не хватает.
Как найти места, где используются реализованные методы, если они закрыты интерфейсом?
Ответ тот же самый. Это тоже легко поддается статическому анализу. Если ваша IDE не может найти реализации, то это её недостаток, а не интерфейсов. Если IDE развивается, то в ближайшем будущем эта функция должна появиться.
Заключение
Интерфейсы в GO позволяют сделать код немного проще. Скрытие лишних зависимостей, простота в расширении кода и легкое тестирование — это те преимущества, которыми обладают интерфейсы в этом языке. Я не утверждаю, что это большое преимущество. Но если вы разрабатываете большой проект, это важно.
Язык GO кажется подходящим инструментом для развития больших проектов. И интерфейсы — это одна из тех фич, которая сделана удачно.
===========
Источник:
habr.com
===========
Похожие новости:
- [PHP, Программирование] Понимаем JIT в PHP 8 (перевод)
- [Тестирование IT-систем, Тестирование веб-сервисов, Тестирование игр, Тестирование мобильных приложений] Как выбрать мобильные девайсы для тестирования и не налажать
- [IT-компании, Информационная безопасность, История IT, Терминология IT] Google просит ИБ-сообщество отказаться от терминов Black\White Hat и заменить их на нейтральные
- [Программирование, Учебный процесс в IT] Как сменить свою специальность на программиста?
- [Администрирование баз данных, Законодательство в IT, Информационная безопасность] Хакер взламывает базы данных MongoDB и требует выкуп с угрозами слить персданные и сообщить в органы защиты GDPR
- [ВКонтакте API, Программирование] Как реализовать свою идею и не сойти с ума на самоизоляции
- [Swift, Разработка под iOS, Тестирование мобильных приложений] Apple WWDC 2020: что нового в тестировании iOS
- [Scala, Программирование] Применение ZIO ZLayer (перевод)
- [ООП, Программирование, Функциональное программирование] SOLID == ООП?
- [Data Mining, Визуализация данных, Natural Language Processing, Программирование, Python] How to find an English teacher. Part 1
Теги для поиска: #_programmirovanie (Программирование), #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_go, #_golang, #_testirovanie (тестирование), #_chistyj_kod (чистый код), #_dizajn_koda (дизайн кода), #_interface, #_programmirovanie (
Программирование
), #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
), #_go
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:32
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В языке GO интерфейсы отличаются от других языков. Они немного лучше чем в других популярных языках с точки зрения дизайна. В этой статье я постараюсь объяснить почему. Я расскажу о преимуществах, приведу примеры и рассмотрю некоторые вопросы, которые могут возникнуть при использовании интерфейсов. В чем особенность интерфейсов в GO? В большинстве языков вы описываете один интерфейс, и реализуете их в других местах явно, указывая, что вы реализуете именно их. Но в GO это не так. Вам не нужно указывать явно, что вы реализуете его в своей структуре. Если у структуры есть все функции и они имеют одинаковую сигнатуру с интерфейсом, то она уже его реализует. Какие преимущество дает эта особенность? Приватный интерфейс Интерфейсы удобно описывать внутри модуля. Объясню почему это хорошо. Предположим вы пишите пакет. По-хорошему он должен минимально зависеть от других пакетов, и для этого вы отгораживаетесь от них интерфейсом. Это позволит вам тестировать ваш пакет изолированно и в случае необходимости подменить внешнюю библиотеку. Но этот интерфейс может быть приватным, т.е. другие пакеты о нем ничего не знают. В конкретный момент нужны конкретные методы от какого-то внешнего объекта, и для этого достаточно иметь интерфейс только с этими конкретными методами. Вам не нужно искать подходящие интерфейсы под создаваемый модуль из внешних или общих пакетов. Рассмотрим пример. Я пишу пакет, отвечающий за авторизацию пользователя. В нем необходимо обращаться к репозиторию пользователя. Нам нужны только несколько методов, опишем их в интерфейсе: package auth
import ( "gitlab.com/excercice_detection/backend" ) type userRepository interface { FindUserByEmail(email string) (backend.User, error) AddUser(backend.User) (userID int, err error) AddToken(userID int, token string) error TokenExists(userID int, token string) bool } // Auth сервис авторизации type Auth struct { repository userRepository logger backend.Logger } // NewAuth создает объект авторизации func NewAuth(repository userRepository, logger backend.Logger) *Auth { return &Auth{repository, logger} } // Autentificate Проверяет существование токена пользователя func (auth Auth) Autentificate(userID int, token string) bool { return auth.repository.TokenExists(userID, token) } Для примера я показал как используется один из методов, на самом деле они используются все. В главном методе main создается и используется объект авторизации: package main
import ( "gitlab.com/excercice_detection/backend/auth" "gitlab.com/excercice_detection/backend/mysql" ) func main() { logger := newLogger() userRepository := mysql.NewUserRepository(logger) err := userRepository.Connect() authService := auth.NewAuth(userRepository, logger) ... При создании объекта авторизации достаточно передать userRepository, у которого реализованы все методы, которые есть в интерфейсе, а пакет mysql при этом ничего не знает об интерфейсе, описанном в сервисе авторизации. Он и не должен об этом знать. Нет лишних зависимостей. Код остается чистым. В других языках программирования вам бы пришлось описывать интерфейс в общем модуле. И указывать в классе репозитория, что он реализует этот интерфейс. А в классе авторизации использовать его. Хотя на самом деле об этом интерфейсе достаточно знать только модулю авторизации, потому что он нужен только ему. Если вы передадите объект, который не реализует нужный интерфейс, вы получите ошибку на этапе компиляции. Такой интерфейс удобно расширять Если вам в будущем пригодятся другие методы из репозитория, вы можете просто добавить их в этот приватный интерфейс. Он немного усложниться, но только внутри этого модуля. Вам не нужно докидывать их в неком общем интерфейсе, а затем описывать методы везде, где он реализуется. Тесты В тестах тоже удобно использовать этот интерфейс. Достаточно подменить методы, которые будут использоваться в модуле. Не нужно подменять ничего лишнего. Пример мока: type userRepositoryMock struct {
user backend.User findUserErr error addUserError error addUserID int addTokenErr error tokenExists bool } func (repository userRepositoryMock) FindUserByEmail(email string) (backend.User, error) { return repository.user, repository.findUserErr } func (repository userRepositoryMock) AddUser(backend.User) (userID int, err error) { return repository.addUserID, repository.addUserError } func (repository userRepositoryMock) AddToken(userID int, token string) error { return repository.addTokenErr } func (repository userRepositoryMock) TokenExists(userID int, token string) bool { return repository.tokenExists } Далее, в тестах, userRepositoryMock можно подсунуть вместо обычного userRepositorу, подставляя нужные значения, которые должна вернуть функции. Как понять, кто на самом деле реализует метод, используемый из интерфейса? Кажется, что закрываясь интерфейсом и не указывая явно кто его реализует, мы теряем знание о том, как на самом деле работает нужная функция. Но, поскольку GO является строго типизированным языком, то узнать кто реализует метод достаточно просто. Например IDE GoLand умеет так делать. Чтобы убедиться, что структура реализует интерфейс, достаточно запустить компилятор. Он покажет, каких методов не хватает. Как найти места, где используются реализованные методы, если они закрыты интерфейсом? Ответ тот же самый. Это тоже легко поддается статическому анализу. Если ваша IDE не может найти реализации, то это её недостаток, а не интерфейсов. Если IDE развивается, то в ближайшем будущем эта функция должна появиться. Заключение Интерфейсы в GO позволяют сделать код немного проще. Скрытие лишних зависимостей, простота в расширении кода и легкое тестирование — это те преимущества, которыми обладают интерфейсы в этом языке. Я не утверждаю, что это большое преимущество. Но если вы разрабатываете большой проект, это важно. Язык GO кажется подходящим инструментом для развития больших проектов. И интерфейсы — это одна из тех фич, которая сделана удачно. =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_proektirovanie_i_refaktoring ( Проектирование и рефакторинг ), #_go |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:32
Часовой пояс: UTC + 5