如何(或者应该如何)使用Go接口来选择两个包之一?

huangapple go评论89阅读模式
英文:

How can I (or should I) use Go interfaces for picking one of two packages?

问题

好的,以下是翻译好的内容:

好的,我还不太明白...。

我有两个模块,它们具有相同的功能(当然是在不同的文件中):

package mod1
func MyFunc() string {
   return "mod1.Myfunc"
}
func Func2() string {
   return "mod1.Func2"
}
package mod2
func MyFunc() string {
  return "mod2.MyFunc"
}
func Func2() string {
   return "mod2.Func2"
}

我在第三个包中正确地定义了一个接口(我想是这样的):

package types

type MyType interface {
  MyFunc() string
  Func2() string
}

我有一段代码可以选择我想要使用的是mod1还是mod2,但我不太明白这段代码应该返回什么:

func mypicker() ????{

}

然后在main函数中,我想根据mypicker来调用mod1.MyFunc()或mod2.MyFunc(),而不知道具体是哪个模块... 类似这样:

func main() {
  p := mypicker()
  fmt.Print(p.MyFunc())
  // 然后
  fmt.Print(p.Func2())
}

我读到接口类似于void *,但显然我没有完全理解。
如果有相关文档、代码或其他有用的资源,请提供。

英文:

OK, I'm not quite getting it....

I have 2 modules I crafted with identical functions (in different files of course):

package mod1
func MyFunc() string {
   return "mod1.Myfunc"
}
func Func2() string {
   return "mod1.Func2"
}
package mod2
func MyFunc() string {
  return "mod2.MyFunc"
}
func Func2() string {
   return "mod2.Func2"
}

I have an interface defined correctly, (I think) in a third package:

package types

type MyType interface {
  MyFunc() string
  Func2() string
}

I have code which can pick whether I want to use mod1 or mod2, but I'm not quite understanding what I should have this code return:

func mypicker() ????{

}

Then in main, I want to somehow call either mod1.MyFunc() or mod2.MyFunc() based on
mypicker, without knowing which it is.... something like this:

func main() {
  p := mypicker()
  fmt.Print(p.MyFunc())
  // and later
  fmt.Print(p.Func2())
}

I read that interfaces are like void *, but clearly I'm not getting the complete picture.
Pointers to docs, code, anything useful would be great.

答案1

得分: 4

你必须小心术语的使用。Go模块和Go包是非常不同的,尽管两者都可以包含在目录中。基本上,如果一个目录中至少有一个Go文件且没有go.mod文件,那么该目录就是一个包。如果一个目录中有一个go.mod文件,那么它被认为是一个模块。通常,一个完整的项目可以是一个单独的模块,go.mod文件位于项目的根目录即可。假设这是你的情况,可以认为每个子目录只是该单个模块中的一个包。

接口与模块或包实际上没有关系,它与类型有关。原因是接口定义了行为,也就是定义了类型必须具备哪些方法才能准确地实现该接口。在你的情况下,你定义了接口中声明的两个函数,但它们不是方法,因为它们只是附加到包的顶级函数。为了使函数成为方法,它必须“附加”到一个类型上。然后,该类型就成为该接口的有效实现。

下面的代码需要进行修改:

package mod1

func MyFunc() string {
   return "mod1.Myfunc"
}

func Func2() string {
   return "mod1.Func2"
}

修改后的代码如下:

package mod1

type MyTypeImpl struct {}

func (m MyTypeImpl) MyFunc() string {
   return "mod1.Myfunc"
}

func (m MyTypeImpl) Func2() string {
   return "mod1.Func2"
}

需要注意的是,上述代码中的函数声明语法是如何将函数“附加”到类型上的,从而使其成为方法,这样MyTypeImpl结构体就成为了你的MyType接口的有效实现。

现在,你可以调用接口方法,而不用考虑底层类型实际上是哪个实现:

var iType MyType
iType = MyTypeImpl{}
iType.MyFunc()

请注意,在上面的最后一行中,我们使用MyTypeImpl来实现接口。一旦将实现分配给具有接口类型的变量,我们就只使用接口并忽略底层实现。当我们调用iType.MyFunc()时,Go会从底层实现中调用正确的方法。

如果我们有100个不同的结构体像MyTypeImpl一样实现了MyType接口,它们都可以作为iType = MyTypeImpl{}行的右侧。这就是接口的作用,只需定义一次,然后在不考虑底层结构体实际上是哪个实现的情况下使用它。

英文:

You have to be careful with terminology. Go modules and Go packages are very different, even though both can be contained by directories. Basically, a directory is a package if it has at least one Go file in it and no go.mod file. If a directory has a go.mod file in it then it's recognized as a module. Generally, a whole project can be a single module with the go.mod file at the root of the project and that's sufficient. Assuming this is your case, move forward thinking that every sub-directory is just a package within that single module.

An interface doesn't really have to do with modules or packages, it has to do with types. The reason being is that an interface defines behavior, meaning it defines what methods are required for a type to accurately implement that interface. In your case, you defined both functions declared in your interface BUT they are NOT METHODS because they are top-level functions only attached to the package. In order for a function to be a method, it must be "attached" to a type. Then, that type becomes a valid implementation of that interface.

This...

package mod1
func MyFunc() string {
   return "mod1.Myfunc"
}
func Func2() string {
   return "mod1.Func2"
}

Needs to become this...

package mod1
type MyTypeImpl struct {}
func (m MyTypeImpl) MyFunc() string {
   return "mod1.Myfunc"
}
func (m MyTypeImpl) Func2() string {
   return "mod1.Func2"
}

The naming could be improved greatly but the point is that the above function declaration syntax is how you "attach" a function to a type, making it a method, which allows that MyTypeImpl struct to now be a valid implementation of your MyType interface.

Now you can call the interface methods without regards to which underlying type is actually the implementation:

var iType MyType
iType = MyTypeImpl{}
iType.MyFunc()

Notice that in that last line, it does not matter that we used MyTypeImpl to implement the interface. Once the implementation is assigned to a variable with the interface type, we just work with the interface and forget the underlying implementation. When we call iType.MyFunc(), Go will call the proper method from the underlying implementation.

If we had 100 different structs that implemented the MyType interface as MyTypeImpl does, they could all work for the right side of that iType = MyTypeImpl{} line. That's the point of an interface, to define it once and then use it without regard to what underlying struct is actually implementing it.

答案2

得分: 3

接口应该与类型一起使用,而不仅仅是普通函数。你可以先阅读一下Go之旅中关于接口的部分。下面是一个与你问题中原始代码相似的示例:

给定接口:

type MyType interface {
  MyFunc() string
  Func2() string
}

你可以有一个类型:

type MyType1 struct{}

func (mt MyType1) MyFunc() string {
  return "MyType1.MyFunc"
}


func (mt MyType1) Func2() string {
  return "MyType1.Func2"
}

类似地:

type MyType2 struct{}

func (mt MyType2) MyFunc() string {
  return "MyType2.MyFunc"
}


func (mt MyType2) Func2() string {
  return "MyType2.Func2"
}

现在,如果你有一个接受MyType接口的函数:

func Foo(m MyType) {
  fmt.Println(m.Func2())
  fmt.Println(m.MyFunc())
}

你可以使用实现该接口的任何类型来调用它:

m1 := MyType1{}
Foo(m1)
m2 := MyType2{}
Foo(m2)

这里有一个Go Playground链接,你可以在其中尝试这个示例。

至于“选择一个类型”,也许你的意思是这样做:

var mi MyType
if (...某个条件...) {
    mi = m1
} else {
    mi = m2
}

// 现在你可以对mi进行任何接口允许的操作,
// 比如调用mi.Func2()等等。

关于问题中“选择两个包中的一个”的部分:

接口是由类型实现的,它们与包和模块是无关的。换句话说,接口和实现它的类型可以在同一个包中,也可以在不同的包或不同的模块中。

英文:

Interfaces should be used with types, not just plain functions. You can start by reading the Tour of Go sequence on interfaces. Here's an example close to your question's original code:

Given the interface:

type MyType interface {
  MyFunc() string
  Func2() string
}

You'd have a type:

type MyType1 struct{}

func (mt MyType1) MyFunc() string {
  return "MyType1.MyFunc"
}


func (mt MyType1) Func2() string {
  return "MyType1.Func2"
}

And similarly:

type MyType2 struct{}

func (mt MyType2) MyFunc() string {
  return "MyType2.MyFunc"
}


func (mt MyType2) Func2() string {
  return "MyType2.Func2"
}

And now, if you have some function that takes your MyType interface:

func Foo(m MyType) {
  fmt.Println(m.Func2())
  fmt.Println(m.MyFunc())
}

You could call it with either of your types that implements that interface:

m1 := MyType1{}
Foo(m1)
m2 := MyType2{}
Foo(m2)

Here's a Go Playground link where you can try this in action.

As for "picking a type", perhaps you mean something like this:

var mi MyType
if (... some condition ...) {
	mi = m1
} else {
    mi = m2
}

// Now you can do with mi whatever its interfaces permits,
// like calling mi.Func2(), etc.

Regarding the "picking one of two packages" part of the question:

Interfaces are implemented by types; they're orthogonal to packages and modules. In other words, an interface and types that implement it can all be in the same package, or in different packages, or in different modules.

huangapple
  • 本文由 发表于 2021年7月1日 05:39:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/68201882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定