такой подход рекомендован в Go


Там есть еще разумная рекомендация — объявлять интерфейсы в тех пакетах, где они используются.

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

А если мне в интерфейсе нужны параметры сложного типа? Тогда как? Где объявлять эти параметры? Объявляю вместе с интерфейсами — и все, получаем полную зависимость от пакета с интерфейсов в пакете реализации.
Можно выделить интерфейсы с такими параметрами в отдельный пакет.
Правильно ли я понимаю, что в случае примера с Repository
type (
	AMQPHandler struct {
		repository Repository
	}

	Repository interface {
		Add(user *User) error
		FindByID(ID string) (*User, error)
		FindByEmail(email string) (*User, error)
		FindByCountry(country string) (*User, error)
		FindByEmailAndCountry(country string) (*User, error)
		Search(...CriteriaOption) ([]*User, error)
		Remove(ID string) error
		// и еще
		// и еще
		// и еще
		// ...
	}
)

Я должен буду преобразовать интерфейс в такие интерфейсы?
type (
	AMQPHandler struct {
		repository Repository
	}

	Repository interface {
		RepositoryAdder
		RepositoryFinderByID
		RepositoryFinderByEmail
		RepositoryFinderByCountry
		RepositoryFinderByEmailAndCountry
		RepositorySearcher
		RepositoryRemover
		// и еще
		// и еще
		// и еще
		// ...
	}

	RepositoryAdder interface {
		Add(user *User) error
	}
	
	RepositoryFinderByID interface {
		FindByID(ID string) (*User, error)
	}
	
	RepositoryFinderByEmail interface {
		FindByEmail(email string) (*User, error)
	}
	
	RepositoryFinderByCountry interface {
		FindByCountry(country string) (*User, error)
	}
	
	RepositoryFinderByEmailAndCountry interface {
		FindByEmailAndCountry(country string) (*User, error)
	}
	
	RepositorySearcher interface {
		Search(...CriteriaOption) ([]*User, error)
	}
	
	RepositoryRemover interface {
		Remove(ID string) error
	}
)
Да, такой вариант возможен.
Но для начала можно попробовать вот так:
Repository interface {
                Adder
		Finder		
                Searcher
		Remover		
	}

Основная идея в том, что можно выделить методы или группы методов, которые можно использовать отдельно друг от друга.
А как стоит поступить, если, к примеру, необходимо одновременно найти по Email и удалить?
Сделать дополнительный интерфейс, который будет это делать?
type RepositoryFindRemover interface {
	RepositoryFinderByEmail
	RepositoryRemover
}
А как стоит поступить, если, к примеру, необходимо одновременно найти по Email и удалить?

ИМХО, лучше сделать type cast.

Вообще композиция в Go лично мне не нравится (я использую Go в домашних проектах года два). Во многих случаях интерфейсы просто не работают — основная проблема с интерфейсом в том, что если он возвращается из функции или передаётся как параметр, то всё, что мы можем с ним сделать — это вызывать функции, из которых состоит этот интерфейс. Этот подход неплохо работает, когда нужно заменить простую утилиту на bash или С из пары сотен строк, или когда самый фундаментальный тип данных всего один и простой (как float64 в этой статье), но на более сложных примерах это очень неудобно.

Например, если у вас есть несколько структур с полем ID типа int64, то вы никак не можете создать функцию, которая бы принимала на вход любой из этих типов и работала с полем ID. Создавать id_getter и id_setter для каждой структуры? Можно, конечно передавать указатель на это поле, но тогда о какой композиции может идти речь?

Примечательно, что в сообществе Go массово используют библиотеки, которые пропитаны рефлексией (которая в Go тоже некрасивая) как бисквит коньяком, типа encoding/json, sqlx, database/sql, html/template и т.д. Потому что иначе это чистый Pascal. К слову, все эти красивые идеи, которые реализованы в Go, Дейкстра сотоварищи сформулировал ещё в 70-е, о чём прекрасно рассказывается в этой лекции.

В общем либо я не умею мыслить как настоящий программист на Go, либо я испорчен той свободой, которую даёт Python. И я уже десять раз пожалел, что начал нынешний домашний проект на Go, тем более, что вся эта шикарная производительность, которую даёт Go, мне нафиг не нужна и не будет.
Например, если у вас есть несколько структур с полем ID типа int64, то вы никак не можете создать функцию, которая бы принимала на вход любой из этих типов и работала с полем ID. Создавать id_getter и id_setter для каждой структуры?


В общем либо я не умею мыслить как настоящий программист на Go, либо я испорчен той свободой, которую даёт Python.


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

И да, тогда два варианта или рефлексия (что медленно) или создавать интерфейс типа:
type IDer interface {
   IDSetter(id string)
   IDGetter() string
}


и реализовывать его для всех структур.

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

Ну, например, ситуация, что структура имеет ID и должна записываться/читаться в/из файл/по сети — то достаточно реализовать IDer или ReaderWriter и больше ничего знать конкретного о структуре не понадобится.

В случае, если нужен доступ к полям структуры, но хочется это сделать без рефлексии, то понадобится нечто подобное:

type CustomPropsExample struct {
    I, J int
    // Sum is not stored, but should always be equal to I + J.
    Sum int `datastore:"-"`
}

func (x *CustomPropsExample) Load(ps []datastore.Property) error {
    // Load I and J as usual.
    if err := datastore.LoadStruct(x, ps); err != nil {
        return err
    }
    // Derive the Sum field.
    x.Sum = x.I + x.J
    return nil
}

func (x *CustomPropsExample) Save() ([]datastore.Property, error) {
    // Validate the Sum field.
    if x.Sum != x.I + x.J {
        return errors.New("CustomPropsExample has inconsistent sum")
    }
    // Save I and J as usual. The code below is equivalent to calling
    // "return datastore.SaveStruct(x)", but is done manually for
    // demonstration purposes.
    return []datastore.Property{
        {
            Name:  "I",
            Value: int64(x.I),
        },
        {
            Name:  "J",
            Value: int64(x.J),
        },
    }
}


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


У языков со статической типизацией не только производительность.

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

Для реализации подобного на языках с динамической типизацией приходится писать больше тестов.

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

type BaseStruct struct {
   ID string
   data interface{}
}

или

type BaseStruct struct {
   ID string
   StructA *StructTypeA
   StructB *StructTypeB
}


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

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

Или тот самый специфический случай.
Например, БД, которая должна расскладывать данные в файл по полям отдельно, но заранее не имеет понятия какая структура БД в конкретном случае.
всё, что мы можем с ним сделать — это вызывать функции, из которых состоит этот интерфейс

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


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

Мне кажется, вы смешали композицию интерфейсов для работы с данными и сами структуры данных. Правда вопрос с совпадением имен методов в 2 интерфейсах требует уточнения.


В общем либо я не умею мыслить как настоящий программист на Go, либо я испорчен той свободой, которую даёт Python.

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

А как стоит поступить, если, к примеру, необходимо одновременно найти по Email и удалить?


Если это не настолько типовая ситуация, чтобы имело смысл реализовывать сдвоенный интерфейс, то можно обойтись с использованием:

data.(FinderByEMail)
data.(Remover)


Если же операция частая, то удобнее будет сделать сборный интерфейс:

type FinderAndRemover interface {
   FinderByEMail
   Remover
}

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