生产者端接口

Go言语中常见100问题-#5 接口污染。在编码的时分,接口应该放在哪里呢?这是Go开发人员经常有误解的一个问题,本文将深入剖析该问题。

在深入探讨问题之前,先对提及的术语做一个界说阐明,保证咱们对其有清晰的理解。

  • 生产者端:接口界说与详细完成在同一个包中,称这种为生产者端接口。像下图所示,接口的界说和详细完成都在foo包中,调用客户端代码在bar包中。

Go言语中常见100问题-#6 生产者端接口

  • 顾客端:接口界说与详细完成不在相同的包中,而是界说在调用的客户端代码地点的包中,称这种为顾客端接口。如下图所示,接口界说在运用方包bar中。

Go言语中常见100问题-#6 生产者端接口

在生产者端界说接口,与详细完成放在一同,这是具有C#或Java背景言语的人惯用写法。可是,在Go言语中,在大多数情况下,咱们不应该采用这种写法。下面经过一个示例进行阐明。

示例中,咱们创立一个特定的包来存储和查询客户数据。同时在该包中界说一个接口,一切对客户数据的操作都经过接口来完成。对应到前面,这种完成就是生产者端接口。


package store
type CustomerStorage interface {
StoreCustomer(customer Customer) error
GetCustomer(id string) (Customer, error)
UpdateCustomer(customer Customer) error
GetAllCustomers() ([]Customer, error)
GetCustomersWithoutContract() ([]Customer, error)
GetCustomersWithNegativeBalance() ([]Customer, error)
}

关于上面在生产者端创立接口,并对外露出这个接口。或许认为有很好的理由这样做,咱们或许会说,1.这样能够将客户端代码与实践完成别离,2. 在测验的时分,调用方很方便Mock一个假的接口完成进行测验。可是,这不是Go中的最佳实践。

正如Go言语中常见100问题-#5 接口污染所说到的,与具有显现完成接口的言语相比,Go中经过隐式完成接口,这会带来一些改变,像其它言语惯用的生产者端接口在Go言语中并不是最佳实践。在大多数情况下,应该遵循Go言语中常见100问题-#5 接口污染说到的准则:应该发现笼统而不是创立笼统。这意味着生产者不能为一切客户端强制一个给定的笼统。相反,由客户端决定它是否需求某种形式的笼统。然后确认最适合它需求的笼统级别。

关于上述示例,或许客户端A不会对解耦它的代码感兴趣,或许客户端B想要解耦它的代码,但只对 GetAllCustomers 办法感兴趣。在这种情况下,能够运用单个办法创立一个接口,引用外部包中的 Customer 结构体。


package client
type customersGetter interface {
GetAllCustomers() ([]store.Customer, error)
}

从包组织引用联系来看,客户端和存储两个包联系如下图。需求注意几点:

  • 由于 customersGetter 接口仅在客户端包中运用,因此该接口能够保持不导出。

  • 咋一看,下图看起来像存在循环依靠。可是,由于隐式满意接口,存储与客户端之间没有循环依靠联系。这也是为什么这种办法在具有显现完成的言语中并不总是可行的原因。

Go言语中常见100问题-#6 生产者端接口

采用在客户端中界说接口的关键点是客户端能够根据需求界说最精确的笼统(像customersGetter只有一个办法),契合接口隔离准则(SOLID中的I),该准则指出不应逼迫任何客户端依靠它不运用的办法。因此,在这种情况下,最好的办法是在生产者端揭露详细完成(办法可导出),让客户端决定怎么运用它以及是否需求笼统。

本文说到了生产者端接口和顾客端接口两个概念,前面剖析了顾客端接口,为了完整性,这儿对生产者端接口也做点剖析。生产者端接口有时分会在规范库中遇到,例如encoding子包中界说了完成的接口,如encoding/json、encoding/binary. 采用这种方式错了吗?前面不是说Go中不引荐生产者端接口吗? 没有错,在这种情况下,encoding包中界说的笼统在规范库中运用,言语设计者知道预先创立这种笼统是有价值的。这满意Go言语中常见100问题-#5 接口污染中的讨论:假如你认为笼统在想象的将来或许有用,可是不能证明这个笼统时有用的,就不要创立笼统。

因此,在大多数情况下,Go言语中的接口应该位于顾客端。可是,在特定情况下,例如,当咱们知道(不是料想)笼统对顾客有帮助时,咱们或许期望将其放在生产者端。假如要这样做,应该尽力让接口尽或许地最小化(接口中的办法仅或许少),像encoding/json中界说的Marshaler接口只包括1个办法,这样增加它的可重用潜力并使其更容易组合。