Структурирование кода Голанга

Стоит ли группировать методы в структурах: Например:

type UserManager struct {
    DB *sql.DB
}

func (m UserManager) Insert (u User) error {...}
func (m UserManager) Delete (u User) error {...}
...

Или проще поддерживать только отдельные функции.

func InsertUser (u User, db *sql.DB) error {...}

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


go
person Alexander    schedule 18.02.2017    source источник
comment
Я предлагаю пойти с первым подходом. Это делает кодовую базу более структурированной и читабельной. Создав экземпляр или UserManager, все приложение, которое можно сделать на нем, будет очень понятным при кодировании.   -  person Mayank Patel    schedule 18.02.2017


Ответы (2)


Ваше второе предложение не является хорошим кодом! Почему? Потому что в лучшем случае функция должна принимать на вход интерфейсы.

Таким образом, функция InsertUser должна выглядеть примерно так, и она будет сочетать ваше первое предложение со вторым:

type Inserter interface {
   Insert(User)error
}
func InsertUser(i Inserter) error {...}

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

person apxp    schedule 18.02.2017
comment
Мне нравится, что этот ответ делает то же самое, что и я, но под совсем другим углом. Я думаю, что это действительно говорит о том, насколько сильно дизайн, основанный на интерфейсе, может конвергировать, что, я думаю, является настоящей красотой Go. - person nothingmuch; 18.02.2017

Либо, либо ни то, ни другое - на мой взгляд, это не имеет значения, потому что идиоматический подход состоял бы в том, чтобы организовать эти понятия с помощью интерфейсов:

package user

type User ...

type Inserter interface { Insert(User) error }
type Deleter interface { Delete(User) error }
type Manager interface { Inserter, Deleter } // bloated interface

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

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

В вашем случае очевидно, что придерживаться первого стиля реализации намного проще:

type userManager struct { ... }
func (userManager) Insert(u User) error { ... }
func (userManager) Delete(u User) error { ... }

userManager — это частный тип, поэтому его можно изменять без опасений, пока он продолжает удовлетворять общедоступные интерфейсы.

Отделение интерфейсов от реализации значительно упрощает их сужение, поэтому вместо простого «менеджера пользователей» или чего-то еще вы можете узнать, какие интерфейсы вам действительно нужны для задач. Между прочим, у этого подхода есть замечательное свойство: он хорошо согласуется с моделью возможностей объекта, что помогает упростить такие вещи, как управление доступом на основе ролей.

person nothingmuch    schedule 18.02.2017