英文:
Basic API in golang antipattern?
问题
请纠正我如果我理解错了,对于我对API的理解是,它是允许我通过接口修改和请求数据的东西,这正是我想在Go中做的。例如,我有一个用户界面:
interface IUser {
GetId() int
GetName() string
GetSignupDate() time
GetPermissions() []IPermission
Delete()
}
在我看来,这已经像是活动记录(active record)了,如果我想创建一个具有新ID的新用户,我将不得不使用new
,因为据我所知,Go不支持静态函数。这意味着我还需要在我的接口中添加一个commit函数,这对我来说更糟糕。我在这里做错了什么?
英文:
Correct me if I'm wrong, but for my understanding of an API is that it is something that allows me to modify and request data through an interface, which is what I want to do in Go. For example I have a user interface:
interface IUser {
GetId() int
GetName() string
GetSignupDate() time
GetPermissions() []IPermission
Delete()
}
This already looks to me like active record and if I want to create a new user with a new id I would have to use new
since Go doesn't support static functions as far as I know. This means I would also need a commit function in my interface, which makes it even worse for me. What am I doing wrong here?
答案1
得分: 2
在Go语言中,接口是行为型的。也就是说,它们更多地描述了一个事物的行为,而不是它的本质。从你的示例来看,你似乎在尝试用Go语言写C#代码,因为你在接口类前面大量使用了"I"。然而,一个只被一个类型实现的接口有点浪费时间。
相反,考虑以下方式:
interface Deleteable { // 你可能会倾向于将其命名为IDeleteable
// 《Effective Go》建议使用Deleter,但语法
// 听起来有点奇怪
Delete() error
}
现在你可以创建一个函数来执行批量删除操作:
func BatchDelete(victims []Deleteable) {
// 做一些批量操作的酷炫事情,连接到数据库,启动一个事务
for _, victim := range victims {
victim.Delete() // 或者以某种方式调用此函数。
}
}
你可能会更快地开始,通过为Update、Serialize等创建一个接口,并在具体的结构体中存储你的实际用户/权限等信息来实现这些方法。(注意,在Go语言中,你不需要声明一个类型实现了某个接口,它会自动发生)。你也不必为每个方法都创建一个单独的接口(Updater、Serializable),而是可以将它们全部捆绑到一个接口中:
type DBObject interface {
Update()
Serialize() RowType
Delete()
}
type User struct {
Id int
Name string
// ... 等等
}
请记住,即使实际的用户对象的表示形式是更加分散的,比如RDF三元组,你的模型仍然可以“填充”一个User对象来从API返回。
英文:
In Go, interfaces are behavioural. That is, they describe what a thing does more than what it is. Your example looks like you're trying to write C# in Go, with your heavy use of I in front of interface classes. However, an interface that is only implemented by one type is a bit of a waste of time.
Instead, consider:
interface Deleteable { // You'd probably be tempted to call this IDeleteable
// Effective go suggests Deleter, but the grammar
// sounds weird
Delete() err
}
Now you can create a function to perform batch deletes:
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.
}
}
You'd probably get started faster by creating an interface for Update, Serialize and so on, and storing your actual users/permissions/etc in concrete structs that implement those methods. (Note in Go you don't have to say that a type implements an interface, it happens "automatically"). You also don't have to have a single interface for each method (Updater, Serializable), but you can bundle them all into one interface:
type DBObject interface {
Update()
Serialize() RowType
Delete()
}
type User struct {
Id int
Name string
// ... etc
}
Remember, your model can always "Fill in" a User object to return from your API, even if the actual representation of the User object is something much more diffuse, e.g. RDF triples.
答案2
得分: 0
我同意@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的使用者决定调用的风格。详见这里。
英文:
I agree with @ZanLynx comments. Go’s standard library seems to favour the interface way for APIs.
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))
}
It may be worth noting that APIs that take in a one-method interface could be re-written as taking a function type.
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))
}
As an API author, one could provide both mechanisms and let the API consumer decide the style of invocation. See http://aquaraga.github.io/functional-programming/golang/2016/11/19/golang-interfaces-vs-functions.html.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论