英文:
Golang code structuring
问题
将方法分组到结构体中是否值得:
例如:
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的最佳实践。
英文:
Is it worth to group methods in structs:
For example:
type UserManager struct {
DB *sql.DB
}
func (m UserManager) Insert (u User) error {...}
func (m UserManager) Delete (u User) error {...}
...
Or is it simpler support just separate functions.
func InsertUser (u User, db *sql.DB) error {...}
While second approach looks simpler at first, in future this way, there may be to many functions in package. Should I make separate package for every domain aggregate? In examples, I've seen so far, there is just model
package.
I've been working mainly with OO languages so need some advice for go best practices here.
答案1
得分: 1
你的第二个建议对于编码来说不太好!为什么呢?因为在最好的情况下,一个函数应该以接口作为输入。
所以一个InsertUser
函数应该像这样,并且它会结合你的第一个和第二个建议:
type Inserter interface {
Insert(User) error
}
func InsertUser(i Inserter) error {...}
在这种情况下,测试你的函数很容易,因为你可以轻松地模拟插入器。
英文:
Your second suggestion is not good go code! Why? Because in the best case a function should take interfaces as an input.
So a InsertUser
function should look something like that and it would combine your first with your second suggestion:
type Inserter interface {
Insert(User)error
}
func InsertUser(i Inserter) error {...}
In that case testing of your function is easy, because you can easy mock the inserter.
答案2
得分: 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
可能是一个具体的行类型,就像你的示例中一样,但也可以将其作为一个不提及这些类型的接口。
如果你编写引用这些接口的函数,那么你可以快速地使用嵌入和提升字段进行组合。
在你的情况下,坚持第一种实现风格显然更简单:
type userManager struct { ... }
func (userManager) Insert(u User) error { ... }
func (userManager) Delete(u User) error { ... }
userManager
是一个私有类型,因此可以随意更改,只要它继续满足公共接口的要求。
将接口与实现解耦使得将它们变窄变得更容易,因此不仅仅是拥有一个“用户管理器”之类的东西,你还可以找出你真正需要的任务接口。顺便说一句,这种方法与对象能力模型非常契合,有助于简化诸如基于角色的访问控制之类的事情。
英文:
Either, or neither - it really doesn't matter in my opinion because the idiomatic approach would be to organize these concepts using interfaces:
package user
type User ...
type Inserter interface { Insert(User) error }
type Deleter interface { Delete(User) error }
type Manager interface { Inserter, Deleter } // bloated interface
User
in this case is probably a concrete row type like in your example, but one could make the case for making it too into an interface that doesn't mention those types.
If you write functions that reference these interfaces, then you can quickly glue together using embedding & promoted fields.
In your case it's obvious that sticking to the first implementation style is much simpler:
type userManager struct { ... }
func (userManager) Insert(u User) error { ... }
func (userManager) Delete(u User) error { ... }
userManager
is a private type, so it can be changed without concern, as long as it keeps satisfying the public interfaces.
Keeping the interfaces decoupled from the implementation makes it much easier to make them narrow, so instead of just having a "user manager" or something, you can find out which interfaces you really need for the tasks. Incidentally, this approach has the nice property that it fits well with the object capability model, which helps to simplify things like role based access control.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论