Golang多方法接口和结构体命名

14
Golang文档提供了明确的指导,如何命名单方法接口(通过添加“-er”)。但是,对于只有一个结构实现的多方法接口,最佳实践是什么?
在C#中,接口将具有“I”前缀,在Java中,类将具有“Impl”后缀。Golang是否有类似的指导方针?
在这里拥有接口的重点是能够为依赖于它的组件进行单元测试而模拟它。
以下是UserManager接口的具体示例。它将被Web API控制器使用,该控制器将HTTP请求转换为对此接口上的方法调用。这个Web API控制器将使用大部分UserManager方法。
type UserManager interface { // Should it be UserManagerInterface ?
    GetUser(id int) User
    CreateUser(user User)
    DeleteUser(id int)
    ResetPassword(id int, newPassword string)
    VerifyEmail(id int)
}

type UserManagerImpl struct { // -Impl, or -Struct, or something else? 

}

4
UserManager 的实现应该反映出它从哪里/如何获取/修改其数据。例如: DBUserManagerInMemoryUserManagerCachingUserManager - user142162
3个回答

6

从Java/C#转到Go需要进行范式转变。

由于接口是隐式实现的,因此它们是在“消费”它们的地方定义的,而不是在实现它们的地方定义的。

因为接口是隐式实现的,所以更喜欢较小的接口,因此关注单方法“Verber”接口。如果您有一个需要读取字节的函数,则可以使用io.Reader并且该函数可以提供具有该方法的任何类型(无论它拥有什么其他方法)。您可能没有一个调用您接口中列出的5个方法的所有函数(如果有,那么该函数可能做得太多了)。

如果您觉得需要为结构体和接口命名相同的名称,并因此需要某种前缀/后缀,则您可能正在错误地考虑它,并且可能需要重新考虑您的设计。

DBAL是仅有的一种存在真正需要更大接口的领域(尽管它应该仍然由较小接口组成)。但在这种情况下,“Impl”并不告诉您任何信息 - Java和C#喜欢拥有毫无意义的接口,Go则不是。如果只有一个实现,那么该接口可能是无用的。

如果将来会有多个实现,则它们之间的差异应指导您的命名。例如,您可能需要一个PostgresUserManagerMongoUserManagerMockUserManager


2
如果您只有一个实现,那么接口可能是无用的。这代表了我的情况。然后我的主要问题是:如何为仅具有结构体的测试创建模拟? - onepiece
4
如果你在使用模拟对象,那么这并不能描述你的情况;因为你有两个实现:生产实现和模拟实现。因此,你需要一个接口。 - Adrian
2
没有提供替代方案,仅仅说“你可能想错了”并不是很有帮助。 - the_marcelo_r
@the_marcelo_r,前面的整个段落提供了一种替代方案。有什么特别需要我澄清的吗? - Adrian
@Adrian,如果一个人想要创建一个接口,其具体的模拟结构将在大量单元测试函数中被利用,那么该接口将需要所有函数,并不一定意味着一个单一的函数将调用“你的接口中列出的所有5个方法”。我在编写某些测试时遇到了困难,而我从这个答案中得到的唯一启示是我一直以来都在错误地思考它,但我不确定哪里错了。也许如果您有一些伪代码/片段来说明您的方法,那会更有帮助? - the_marcelo_r
显示剩余4条评论

2

您真的需要将UserManagerImpl结构设为公共的吗?通常情况下,公共接口和相应的私有实现是很常见的。请查看这里这里

type Store interface {
    RunRoot() string
    GraphRoot() string
    ...
}

type store struct {
    ...
}

func New() Store {
    return &store{}
}

func (s *store) RunRoot() string {
    return s.runRoot
}

func (s *store) GraphRoot() string {
    return s.graphRoot
}

我的意思是,如果你想出了一个合适的接口名称,你仍然可以在实现中使用它。但是总的来说,最好按照其本质来命名,而不考虑给定语言的最佳实践。项目是独特的,几乎不可能为该语言的所有用例制定一份最佳实践清单。


1

@Ivan Velichko一样,我也曾经将接口设为公共的,结构体设为私有的,就像这样:

type Service interface {
    Do() error
}

type service struct {
    dependency dependencies.Dependency
}

func (s *service) Do() error {
    return s.depedency.Do()
}

func NewService() Service {
    return &service{}
}

然而,如果由于某些原因您必须使结构体或其属性公开,并且您不想编写大量的getter或setter,请考虑以下示例:

interface名称更具体化以执行操作

根据这些文章:

您应该:

按照惯例,单方法接口的命名方式是使用方法名称加上-er后缀或类似修改来构造代理名词:Reader、Writer、Formatter、CloseNotifier等。

type ServiceWorker interface {
    HandleMessages() error
}

type Service struct {
    dependency dependencies.Dependency
}

func (s *service) HandleMessages() error {
    return s.depedency.Do()
}

func NewServiceWorker() Service {
    return &service{
        Property: "property"
    }
}

创建用于获取/修改结构体的函数

type Service interface {
    Do() error
    Service() *service // this will handle getting and modifying your struct
}

type service struct {
    dependency dependencies.Dependency
    Property string // property to want to expose
}

func (s *service) Do() error {
    return s.depedency.Do()
}

func (s *service) Service() *service {
    return s
}

func NewService() Service {
    return &service{
        Property: "property"
    }
}
//...
func main() {
    s := pkg.NewService()
    fmt.Println("p1:", s.Service().Property) // p1: property

    s.Service().Property = "second"
    fmt.Println("p2:", s.Service().Property) // p2: second
}

定义 getters 和 setters

以更 Java/C# 的方式定义 getters 和 setters:

type Service interface {
    Do() error
    Get() service
    Set() *service
}

type service struct {
    dependency dependencies.Dependency
    Property string // property to want to expose
}

func (s *service) Do() error {
    return s.depedency.Do()
}

func (s service) Get() service {
    return s
}

func (s *service) Set() *service {
    return s
}

func NewService() Service {
    return &service{
        Property: "property"
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接