是否值得在结构中对方法进行分组: 例如:
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
个包。
我一直主要使用OO语言,所以需要一些建议来获取最佳实践。
答案 0 :(得分:1)
你的第二个建议是不好的去代码!为什么?因为在最好的情况下,函数应该将接口作为输入。
所以InsertUser
函数看起来应该是这样的,它会将你的第一个与你的第二个建议结合起来:
type Inserter interface {
Insert(User)error
}
func InsertUser(i Inserter) error {...}
在这种情况下,您的功能测试很简单,因为您可以轻松模拟插入器。
答案 1 :(得分:1)
要么是,要么两者都没有 - 在我看来这并不重要,因为惯用的方法是使用接口来组织这些概念:
package user
type User ...
type Inserter interface { Insert(User) error }
type Deleter interface { Delete(User) error }
type Manager interface { Inserter, Deleter } // bloated interface
在这种情况下, User
可能是一个具体的行类型,就像你的例子中一样,但是人们可以将它变成一个不提及这些类型的接口。
如果编写引用这些接口的函数,则可以使用embedding & promoted fields快速粘合在一起。
在您的情况下,显而易见的是坚持第一种实现方式要简单得多:
type userManager struct { ... }
func (userManager) Insert(u User) error { ... }
func (userManager) Delete(u User) error { ... }
userManager
是一种私有类型,因此只要它能够满足公共接口,就可以无需担心地进行更改。
保持接口与实现分离使得更容易使它们变窄,因此不仅仅是拥有一个"用户管理器"或者其他什么,你可以找到你真正需要的任务接口。顺便说一句,这种方法具有很好的属性,它很适合object capability model,这有助于简化基于角色的访问控制等事情。