【问题标题】:Returning interfaces for nested structs返回嵌套结构的接口
【发布时间】:2019-08-14 19:33:28
【问题描述】:

我正在尝试通过使用接口来打破合理规模的 golang 项目中的循环依赖关系。我有一些这样的嵌套结构:

// these are in one package...
type config struct {
    region string
}

type system struct {
    name string
    config config
}

func (s system) getName() string {
    return s.name
}

func (s system) getConfig() config {
    return s.config
}

func (c config) getRegion() string {
    return c.region
}

在另一个想要使用它们的包中,我声明了一些相应的嵌套接口:

type iConfig interface {
    getRegion() string
}

type iSystem interface {
    getName() string
    getConfig() iConfig
}

// and has functions like
func New(system iSystem) {
    fmt.Printf("region=%s", system.getConfig().getRegion())
}

但是当我尝试像这样使用它们时:

theSystem := system{
    name: "testName",
    config:config{
        region:"testRegion",
    },
}

New(theSystem)      // doesn't work

我收到一个错误:

cannot use theSystem (type system) as type iSystem in argument to New:
system does not implement iSystem (wrong type for getConfig method)
    have getConfig() config
    want getConfig() iConfig

似乎因为我的具体system 结构返回一个具体类型config,Go 认为它不满足iConfig 接口——即使我可以通过iConfig 接口直接使用config。我期待config 隐含地满足iConfig 接口,但这并没有发生。我该如何解决这个问题?

这是Go Playground的链接。

【问题讨论】:

  • “我期待 config 隐式满足 iConfig 接口,但事实并非如此。” 这实际上是正在发生的事情,只是因为 config 实现了iConfig 这并不意味着 system 实现 iSystem,方法签名不同,这就是您收到错误的原因。通过havewant 的解释,错误消息非常清楚。要修复它,您可以更新systemgetConfig 方法的签名以返回接口而不是具体类型。
  • 我不能这样做,因为我试图让每个包为它需要的对象类型声明自己的私有接口。我有多个包,每个包都声明了它们需要的具体对象的方法的一些变体。如果我再次走上单一接口的路线,我最终会像以前一样使用循环导入。

标签: go interface


【解决方案1】:

对于您的情况,您可以实现三个包来解决循环导入:

  1. 配置包

获取应用程序的配置并以方便的方式为其组件提供配置。

package config

type IConfig interface {
    GetRegion() string
}

type config struct {
    region string
}

func New(region string) IConfig {
    return &config{
        region: region,
    }
}

func (c config) GetRegion() string {
    return c.region
}

  1. 系统包

根据应用程序的配置生成系统。

package system

import (
    // "config"
)

type ISystem interface {
    GetName() string
    GetConfig() config.IConfig
}

type system struct {
    name string
    config config.IConfig
}

func New(name string, cfg config.IConfig) ISystem {
    return &system{
        name: name,
        config: cfg,
    }
}

func (s system) GetName() string {
    return s.name
}

func (s system) GetConfig() config.IConfig {
    return s.config
}
  1. 第三方包

使用示例:

package main

import (
    "fmt"
    // "config"
    // "system"
)

func UseConfig(cfg config.IConfig) {
    fmt.Printf("region=%s\n", cfg.GetRegion())
}

func UseSystem(s system.ISystem) {
    fmt.Printf("region=%s\n", s.GetConfig().GetRegion())
}

func main() {
    cfg := config.New("myregion")
    s := system.New("mysystem", cfg)

    UseConfig(cfg)
    UseSystem(s)
}

【讨论】:

  • 这可行,但使用这种方法,configsystem 上的任何私有方法都需要公开。有没有办法将这些方法保密?
  • @jbrown 没有,但只有你要在包外使用的。接口是与您的应用程序单元交互的契约,即您的包的使用方式。所以,如果你想在外面使用一个方法,它必须是公共的。
【解决方案2】:

接口是其他类型可以遵守的签名/合同。

签名可以包括一个或多个方法签名,这意味着方法名称、参数(包括类型)和返回参数。如果它不包含任何方法,它实际上是臭名昭著的interface{} 类型,每个类型都遵守。

要遵守接口,类型必须严格实现完整的签名,包括所有传递和返回的参数及其类型。

接口和结构是不同的类型。

因此,在下面的例子中Type2没有实现Intf2

type Intf1 interface{
    Bar()
}

type Type1 struct {}
func (s SomeType) Bar() {}

type Intf2 interface{
    Foo() Intf1
}

// Type2 does not implement Intf2
type Type2 struct {}
func (s Type2) Foo() Type1 {}

// Won't compile
var _ Intf2 = Type2{}

因为go编译器认为Intf2上的方法Foo的签名不同,因为返回类型不同。编译器不会推断返回类型也实现了接口,因为这样做会带来很多复杂性。

如果您希望此示例正常工作,您需要将 Type2 更改为:

// Type2 does not implement Intf2
type Type2 struct {}
func (s Type2) Foo() Intf1 {}

这也适用于传递的参数,而不仅仅是返回参数。

现在,关于循环依赖,我建议你在第三个包中公开你的接口,它充当胶水和顶级包。常见的事情之一是拥有一个与接口组成的主包以实现其主要目的。

例子:

pkg config
    type Source interface{}
    type Parser interface{}

    pkg parsers
        pkg jsonparser
            implements config.Parser
        pkg yamlparser
            implements config.Parser

    pkg sources
        pkg filesource
            implements config.Source
```

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-04
    • 2014-07-31
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2018-12-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多