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条)

报告相同问题?

悬赏问题

  • ¥17 pro*C预编译“闪回查询”报错SCN不能识别
  • ¥15 微信会员卡接入微信支付商户号收款
  • ¥15 如何获取烟草零售终端数据
  • ¥15 数学建模招标中位数问题
  • ¥15 phython路径名过长报错 不知道什么问题
  • ¥15 深度学习中模型转换该怎么实现
  • ¥15 HLs设计手写数字识别程序编译通不过
  • ¥15 Stata外部命令安装问题求帮助!
  • ¥15 从键盘随机输入A-H中的一串字符串,用七段数码管方法进行绘制。提交代码及运行截图。
  • ¥15 TYPCE母转母,插入认方向