пятница, 4 декабря 2020 г.

Go style guides: избегайте встраивания типов в общедоступные структуры

Встроенные типы содержат информацию о реализации, препятствуют развитию типов и приводят к неясной документации.

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

type AbstractList struct {}

// Add добавляет объект в список.
func (l *AbstractList) Add(e Entity) {
    // ...
}

// Remove удаляет объект из списка.
func (l *AbstractList) Remove(e Entity) {
    // ...
}

Неудачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    *AbstractList
}

Более удачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    list *AbstractList
}

// Add добавляет объект в список.
func (l *ConcreteList) Add(e Entity) {
    l.list.Add(e)
}

// Remove удаляет объект из списка.
func (l *ConcreteList) Remove(e Entity) {
    l.list.Remove(e)
}

Go позволяет встраивание типов как компромисс между наследованием и композицией. Внешний тип получает неявные копии методов встроенного типа. Эти методы по умолчанию делегируются тому же методу встроенного экземпляра.

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

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

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

Менее удачный вариант:

// AbstractList - это обобщенная реализация
// для разного рода списков сущностей.
type AbstractList interface {
    Add(Entity)
    Remove(Entity)
}

// ConcreteList - это список сущностей.
type ConcreteList struct {
    AbstractList
}

Более удачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    list AbstractList
}

// Add добавляет объект в список.
func (l *ConcreteList) Add(e Entity) {
    l.list.Add(e)
}

// Remove удаляет объект из списка.
func (l *ConcreteList) Remove(e Entity) {
    l.list.Remove(e)
}

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

  • Добавление методов во встроенный интерфейс - критическое изменение.
  • Удаление методов из встроенной структуры - критическое изменение.
  • Удаление встроенного типа - критическое изменение.
  • Замена встроенного типа, даже альтернативой, удовлетворяющей тому же интерфейсу, является критическим изменением.

Хотя написание методов делегирования утомительно, дополнительные усилия скрывают детали реализации, оставляют больше возможностей для изменений, а также устраняют косвенное указание на обнаружение полного интерфейса List в документации.


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


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

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