Это не очередная статья на тему "Является ли 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 - довольно новый язык со своей спецификой, однако ничто не мешает использовать его, как Вам удобно.

Комментарии (4)