вторник, 9 июня 2020 г.

Система типов Golang

Работать с новым языком - настоящая борьба, особенно если тип не похож на тот, что вы видели ранее.

Go считается объектно-ориентированным языком, хотя в нем отсутствует иерархия типов. У него нетрадиционная система типов. Ожидается, что на этом языке все будет иначе, поскольку традиционные парадигмы не всегда помогают пользователям Go.

Ход программы в первую очередь, типы потом

В Go ход и поведение программы не связаны с абстракциями. Вы не начинаете программировать, думая о типах, а скорее о ходе/поведении. По мере того, как вам нужно представлять свои данные более изощренными способами, вы начинаете представлять свои типы.

Роб Пайк, один из создателей Go, поделился своими мыслями о разделении данных и поведения:

... более важной идеей является разделение понятий: данные и поведение - это две разные концепции в Go, не объединенные в одно понятие "класс". - Роб Пайк

Go уделяет большое внимание модели данных. Структуры (которые являются агрегатными типами) обеспечивают легкий способ представления данных. Отсутствие иерархии типов помогает структурам оставаться тонкими, структуры никогда не представляют слои и уровни унаследованного поведения, а только поля данных. Это делает их ближе к структурам данных, которые они представляют, а не к поведению, которое они дополнительно обеспечивают.

Встраивание это не наследование

Повторное использование кода обеспечивается не иерархией типов, а композицией. Языковые экосистемы с классическим наследованием часто страдают от чрезмерного уровня косвенности и преждевременных абстракций, основанных на наследовании, что впоследствии делает код сложным и не поддерживаемым.

Вместо обеспечения иерархии типов Go позволяет создавать и отправлять методы через интерфейсы. Язык допускает встраивание, и большинство людей предполагает, что язык имеет некоторую ограниченную поддержку типов подклассов - это не так.

Встраивание на самом деле мало чем отличается от наличия обычного поля, но позволяет встраивать методы встроенного типа непосредственно в новый тип.

Рассмотрим следующую структуру:

type File struct {
    sync.Mutex
    rw io.ReadWriter
}

Затем объекты File будут напрямую иметь доступ к методам sync.Mutex:

f := File{}
f.Lock()

Это ничем не отличается от предоставления методов Lock и Unlock из File и заставляет их работать в поле sync.Mutex. Это не использование подклассов.

Полиморфизм

Из-за отсутствия подкласса полиморфизм в Go достигается только с помощью интерфейсов. Методы отправляются во время выполнения в зависимости от конкретного типа.

var r io.Reader

r = bytes.NewBufferString("hello")

buf := make([]byte, 2048)
if _, err := r.Read(buf); err != nil {
    log.Fatal(err)
}

Выше r.Read будет отправлен в (*Buffer).Read.

Обратите внимание, что встраивание не является подклассом, типы встраивания не могут быть назначены тому, что они встраивают. Следующий код не будет компилироваться:

type Animal struct {}

type Dog struct {
    Animal
}


func main() {
    var a Animal
    a = Dog{}
}
// НЕ КОМПИЛИРУЕТСЯ

Нет явных реализаций интерфейса

Go не имеет инструментов, которые явно позволяют вам сказать, что вы реализуете интерфейс. Предполагается, что вы реализуете интерфейс, если сигнатура метода совпадает с сигнатурой в определении интерфейса.

Как это масштабируется? Возможно ли случайно реализовать интерфейсы, которые вы не хотели реализовывать? Хотя механически, возможно, для пользовательской базы никогда не было проблемой, чтобы реализация одного интерфейса была ошибочно принята за другой. Интерфейсы часто сильно различаются, или это признак того, что может не потребоваться второй интерфейс, если два интерфейса очень похожи.

Существует культура не вводить новые интерфейсы, а предпочитать использовать те, которые предоставляются стандартной библиотекой, или использовать установленные сообществом. Эта культура также уменьшает количество похожих интерфейсов.

Нет заголовочных файлов

Нет заголовочных файлов и нет культуры "давайте сначала введем интерфейсы". Если вы не хотите предоставлять несколько реализаций одного и того же высокоуровневого поведения, вы не вводите интерфейсы.

Шаблоны именования, основанные на соглашениях об инверсии зависимостей других языков, являются анти-шаблонами в Go. Присвоение имен таким стилем не вписывается в экосистему Go.

type Banana interface {
    //...
}
type BananaImpl struct {}
// АНТИ-ШАБЛОН В GO

Еще одна вещь ... Go предпочитают небольшие интерфейсы. Вы всегда можете встраивать интерфейсы позже, но вы не можете разложить большие.

Нет конструкторов

Go не имеет конструкторов, поэтому не позволяет переопределить конструктор по умолчанию. Конструкция по умолчанию всегда приводит к полям с нулевым значением.

Go имеет философию использовать нулевое значение для представления значения по умолчанию. Максимально используйте нулевое значение, чтобы обеспечить поведение по умолчанию.

Некоторые структуры могут потребовать дополнительной работы, такой как проверка, открытие соединения и т. д., прежде чем стать полезным для пользователя. В таких случаях предпочтительны функции инициализации.

func NewRequest(method, url string, body io.Reader) (*Request, error)

NewRequest проверяет method и url, устанавливает правильные внутренние параметры для чтения из данного тела и возвращает запрос.

Nil приемники

Nil - это значение, nil-значение типа может реализовывать поведение. Разработчики не должны предоставлять конкретные типы для реализации отсуствия операций (noop).

Если вы вводите интерфейс только для того, чтобы обеспечить конкретную реализацию вашего конкретного типа, не делайте этого.

Ниже регистрация событий будет noop для значений nil.

type Event struct {}

func (e *Event) Log(msg string) {
    if e == nil {
        return
    }
    // Записать сообщение о событии...
}

Затем пользователь может использовать значение nil для поведения noop:

var e *Event
e.Log("this is a message")

Нет дженериков

В Go нет дженериков.

Продолжаются разговоры о том, какие дженерики подойдут для Go. Учитывая уникальную систему типов, нелегко просто скопировать существующий подход и предположить, что он будет полезен для большинства и будет ортогонален существующим языковым возможностям.


Читайте также:


Комментариев нет:

Отправить комментарий