Базовый API в антипаттерне golang?

Поправьте меня, если я ошибаюсь, но, насколько я понимаю, API — это то, что позволяет мне изменять и запрашивать данные через интерфейс, что я и хочу делать в Go. Например, у меня есть пользовательский интерфейс:

interface IUser {
  GetId() int
  GetName() string
  GetSignupDate() time
  GetPermissions() []IPermission
  Delete()
}

Мне это уже кажется активной записью, и если я хочу создать нового пользователя с новым идентификатором, мне придется использовать new, поскольку, насколько я знаю, Go не поддерживает статические функции. Это означает, что мне также понадобится функция фиксации в моем интерфейсе, что делает ее еще хуже для меня. Что я здесь делаю неправильно?


person Vic    schedule 27.06.2015    source источник
comment
Вы не совсем ясно представляете, как бы вы хотели выглядеть API. Тем не менее, просто привыкните к тому, как Go работает с API, и используйте это. Посмотрите, как входящие в комплект модули ввода-вывода работают с устройствами чтения, записи и различными типами буферов. Не пытайтесь копировать Java, C# или любой другой шаблон, потому что это покажется НЕПРАВИЛЬНЫМ любому другому программисту на Go.   -  person Zan Lynx    schedule 28.06.2015
comment
Еще один комментарий о добавлении функций в интерфейсы. Попробуйте добавить еще один интерфейс. Если вам нужно, чтобы объект был сохранен в базе данных, у вас может быть второй интерфейс, который делает это, а затем вы реализуете его в своей структуре BasicUser.   -  person Zan Lynx    schedule 28.06.2015


Ответы (2)


В Go интерфейсы поведенческие. То есть они описывают то, что вещь делает больше, чем то, чем она является. Ваш пример выглядит так, как будто вы пытаетесь написать С# на Go с интенсивным использованием I перед классами интерфейса. Однако интерфейс, реализованный только одним типом, — пустая трата времени.

Вместо этого рассмотрите:

interface Deleteable {  // You'd probably be tempted to call this IDeleteable
                        // Effective go suggests Deleter, but the grammar 
                        // sounds weird
    Delete() err
}

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

func BatchDelete(victims []Deleteable) {
    // Do some cool things for batching, connect to db, start a transaction
    for _, victim := range(victims) {
        victim.Delete()  // Or arrange for this function to be called somehow.
    }
}

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

type DBObject interface {
    Update()
    Serialize() RowType
    Delete()
}

type User struct {
    Id int
    Name string
    // ... etc
}

Помните, что ваша модель всегда может «заполнить» объект пользователя для возврата из вашего API, даже если фактическое представление объекта пользователя является чем-то гораздо более расплывчатым, например. RDF тройки.

person Danver Braganza    schedule 27.06.2015
comment
Но как только я добавлю функции для доступа и изменения данных, я перепутаю объект и структуру данных, что является антипаттерном programmers.stackexchange.com/questions/119352/ Как решить эту проблему ? - person Vic; 28.06.2015
comment
@Vic: Является ли эта ваша озабоченность антипаттерном реальной проблемой или проблемой в какой-то теории журавля в небе? Если это теоретическая проблема, игнорируйте ее и делайте то, что работает. Не обращайте внимания на теорию, потому что она бесполезна. - person Zan Lynx; 29.06.2015

Я согласен с комментариями @ZanLynx. Стандартная библиотека Go, похоже, предпочитает интерфейс для API.

package main

import "fmt"

type S string
type I interface{ M() S }
func (s S) M() S { return s }
func API(i I)            I { return i.M() }

func main() {
  s := S("interface way")
  fmt.Println(API(s))
}

Возможно, стоит отметить, что API-интерфейсы, использующие интерфейс с одним методом, могут быть переписаны как принимающие тип функции.

package main

import "fmt"

func API(f func() string) string { return f() }

func main() {
  f := func() string { return "higher-order way" }
  fmt.Println(API(f))
}

Как автор API, можно предоставить оба механизма и позволить потребителю API выбрать стиль вызова. См. http://aquaraga.github.io/functional-programming/golang/2016/11/19/golang-interfaces-vs-functions.html.

person Tom L    schedule 24.08.2020