Это не очередная статья на тему "Является ли Go ООП языком?". Я лишь хочу показать один простой прием, который позволил мне быстрее понять идею интерфейсов в go и в принципе идеологию языка.
Допустим, у нас есть тривиальная задача - создать структуру, которая реализует интерфейс “Duck”, включающий в себя три метода: fly, swim, quack. Условно, он будет выглядеть так:
type Duck interface {
Fly() error
Swim() error
Quack(string) error
}
Далее создадим структуру Goose:
type Goose struct {
Name string
}
В типичных ООП языках, как, например, Java, мы бы просто написали: class Goose implements Duck
, после чего в классе Goose необходимо было бы реализовать все методы интерфейса Duck. В golang все немного не так. Чтобы заставить структуру реализовывать методы интерфейса, нам необходимо присвоить в переменную типа интерфейса экземпляр структуры:
var whiteGoose Duck = Goose{Name: "White"}
Это типичная реализация интерфейсов в golang, которая представлена в официальном туториале, и с которой сталкивался, пожалуй, каждый, кто работал с интерфейсами в go. После такого присваивания, мы получаем предупреждение, что у данной структуры не реализованы методы интерфейса Duck.
Только после того, как мы реализуем у структуры все методы интерфейса, это сообщение пропадет.
func (g Goose) Fly() {
}
func (g Goose) Swim() {
}
func (g Goose) Quack(sound string) {
fmt.Println(sound)
}
Это стандартная и наиболее часто встречающаяся реализация, однако она имеет несколько недостатков. Во-первых, ошибку компиляции мы получаем только при попытке присвоения экземпляра структуры интерфейсу. Но что, если мы и не собираемся создавать экземпляры? Что если мы пишем некий SDK, который будет использоваться в дальнейшем пользователями по их усмотрению? Во-вторых, что если мы хотим сделать структуру приватной? Допустим, мы хотим скрыть реализацию методов, а для работы в клиентском коде использовать только инициализацию и интерфейсы с описанием сигнатур используемых методов. Как же нам добиться чего-то более похожего на class Goose implements Duck
?
Для этого нам достаточно создать конструктор. Действительно, что мешает нам написать функцию, которая будет отвечать за инициализацию объекта? А чтобы заставить структуру реализовывать нужные нам методы, достаточно указать в качестве возвращаемого значения наш интерфейс. Такой конструктор будет выглядеть примерно так:
func NewGoose(name string) Duck {
return goose{name}
}
А в клиентском коде нам остается только вызвать этот конструктор:
var greyGoose = NewGoose("Grey")
В итоге мы имеем структуру с описанными полями, методами и полноценным конструктором для инициализации - чем не привычный большинству читателей классический класс?:)
Конечно, технически это абсолютно одно и то же, однако теперь нам доступны те плюсы, о которых я упоминал, а также (по своему опыту и глядя на разработчиков, пришедших на проект после меня) такой конструктор сильно облегчает жизнь. Golang - довольно новый язык со своей спецификой, однако ничто не мешает использовать его, как Вам удобно.