dongzhang1987 2018-09-15 23:18
浏览 39
已采纳

使用接口作为参数创建另一个包的等效接口

I am practicing writing idiomatic Go code and discovered that interfaces should be declared in packages which are consuming them since they're implicit. However I came to this situation where by in the second package (package b) I want a function to call the receiver function of a struct in package a without coupling it tightly.

So naturally, I declare an interface in package b with the signature of the function I want to call from package a. The problem is that this function accepts an argument of a certain type which is an interface declared in package a. As I don't want package b to import package a, I defined an interface in package b with the exact same signature as the one which exists in package a. The playground link below shows the example code.

Playground

package main

import (
    "fmt"
    "log"
)

func main() {
    manager := &Manager{}
    coach := NewRunnerCoach(manager)
    fmt.Println("Done")
}

// package a

type Runner interface {
    Run()
}

type Manager struct {
}

func (o *Manager) RegisterRunner(runner Runner) {
    log.Print("RegisterRunner")
}

func (o *Manager) Start() {
    log.Print("Start")
}

// package b

type RunnerCoach struct {
    runner *FastRunner
}

func NewRunnerCoach(registerer runnerRegisterer) *RunnerCoach {
    runnerCoach := &RunnerCoach{&FastRunner{}}
    registerer.RegisterRunner(runnerCoach.runner)
    return runnerCoach
}

type FastRunner struct {
}

func (r *FastRunner) Run() {
    log.Print("FastRunner Run")
}

// define ther registerer interface coach is accepting
type runnerRegisterer interface {
    RegisterRunner(runner RunnerB)
}

// declaring a new interface with the same signature because we dont want to import package a
// and import Runner interface
type RunnerB interface {
    Run()
}

This code does not compile. So the question here is that, am I using interface wrongly or should concrete types be defined in a separate package or lastly, is there a better code pattern for the problem I'm trying to solve?

EDIT: To clarify, package a and b does not import each other. The main() code exists in a separate package which connects these two.

  • 写回答

2条回答 默认 最新

  • dongwoqin7034 2018-09-16 07:02
    关注

    IIUC, your question is not about packages but boils down to whether a function (or method) can be typecast to another function which takes arguments with equivalent, but not the same interface types.

    Something like this: (Go Playground)

    package main
    
    type I1 interface{}
    
    func f1(x I1) {}
    
    func main() {
        f := (func(interface{}))(f1)
        f(nil)
    }
    

    Compilation error: ./g.go:8:26: cannot convert f1 (type func(I1)) to type func(interface {})

    The answer appears to be no, because Go doesn't consider func (I1) to be equivalent to func (interface{}). The Go spec says this

    A function type denotes the set of all functions with the same parameter and result types.

    The types func (I1) and func (interface{}) do not take the same parameters, even though I1 is defined as interface{}. Your code fails to compile for a similar reason because func (runner RunnerB) is not the same as func (runner Runner) and hence the method set of *Manager is not a superset of the interface runnerRegisterer.

    Coming to your original question:

    I am practicing writing idiomatic Go code and discovered that interfaces should be declared in packages which are consuming them since they're implicit.

    Yes, the idea is good but it doesn't apply to your implementation the way you think it does. Since you're expecting to have different implementations of runnerRegisterer and they must all have a method with the same signature using the Runner interface, it makes sense to define Runner in a common place. Also, as seen above Go wouldn't allow you to use a different interface in the method signatures anyway.

    Based on my understanding of what you're trying to achieve, here's how I think you should re-arrange your code:

    1. Define RunnerRegisterer (note: this is public) and Runner in one package.
    2. Implement your RunnerCoach in the same package and use the above interfaces. Your RunnerCoach consumes types that implement the interfaces, so it defines them.
    3. Implement your runners in another package. You don't define the Runner interface here.
    4. Implement your Manager in another package, which uses the interface Runner defined in RunnerCoach's package because it must take that type as an argument if it wants to be used as a RunnerRegisterer.
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 yolov8边框坐标
  • ¥15 matlab中使用gurobi时报错
  • ¥15 WPF 大屏看板表格背景图片设置
  • ¥15 这个主板怎么能扩出一两个sata口
  • ¥15 不是,这到底错哪儿了😭
  • ¥15 2020长安杯与连接网探
  • ¥15 关于#matlab#的问题:在模糊控制器中选出线路信息,在simulink中根据线路信息生成速度时间目标曲线(初速度为20m/s,15秒后减为0的速度时间图像)我想问线路信息是什么
  • ¥15 banner广告展示设置多少时间不怎么会消耗用户价值
  • ¥16 mybatis的代理对象无法通过@Autowired装填
  • ¥15 可见光定位matlab仿真