Golang中为什么无法从main包调用包含方法集合的接口?

Golang中为什么无法从main包调用包含方法集合的接口? 我对Go语言还非常陌生,正在尝试理解封装在Go中是如何实际工作的。

我有以下结构:

-- package a
    -a_core.go
    -a.go
    -models.go
-- main.go

在models.go中,我有用于API调用的请求和响应的结构体。

a.go中有一个私有的空结构体和一个我希望公开的、包含各种方法的公共接口。 a_core.go只包含一些业务逻辑,这些逻辑将在我的接口实现中被调用。 然后我有一个main.go,在那里我直接调用公共接口。

package a

type myFunction struct{}

type MyFunc interface {
	Create(myData *MyData) (*MyData, error)
	Fetch(test string)
	Delete(test string)
}

//可以公开访问的具体实现
func (a *myFunction) Create(data *MyData) (*MyData, error) {
	return nil, nil	
}

func (a *myFunction) Fetch(test string) {

}

func (a *myFunction) Delete(test string) {

}

在main.go中,我首先创建带有值的MyData指针来调用接口

    data := &a.MyData{
     /////
   }

   result, err := a.MyFunc.Create(data)

当我这样做时,我得到以下错误:

调用a.MyFunc.Create的参数太少

无法将data(类型为*a.MyData的变量)用作a.MyFunc值传递给a.MyFunc.Create:缺少方法CreatecompilerInvalidIfaceAssign

请问我哪里做错了?


更多关于Golang中为什么无法从main包调用包含方法集合的接口?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

大家好, 这些回复很有帮助。那个例子只是一个简单的示例,而我正在构建一个库,所以,基于我在C#和Java方面的背景,我需要通过接口来公开函数。

更多关于Golang中为什么无法从main包调用包含方法集合的接口?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我并非有意要回答这个问题;我在上一篇文章中已经说过,你的答案是正确的 🙂。我的观点是,除了展示如何在 Go 中使用接口之外,在这种情况下,在这里使用接口没有任何意义。你可以直接使用 MyFunction(或你上一篇文章中的 MyStruct):

myStruct := MyStruct{}

myResult := myStruct.MyPointerReceiverMethod()

你的答案是正确的,因为它展示了如何将具体类型转换为接口并在该接口上调用方法。我怀疑 Gbubemi(或其他可能看到这篇论坛帖子的读者)的下一个问题可能是:为什么首先要使用接口,这正是我想在这里评论的。

肖恩

感谢你的意见,但除非我误解了什么,否则我认为你的日志记录器示例并没有解决这个问题。

最初的问题是如何调用一个拥有指针接收器的方法。

我能找到的最佳资料展示了分两步走的方法:先初始化结构体,然后用该结构体的地址来初始化接口。

通过这个接口,你就可以调用那个拥有指针接收器的方法了。

这并不完全优雅,但确实有效。如果有一个更简洁的方法,我很乐意看到,但目前,这就是我能想到的最好方案:

myStruct := MyStruct{} 

var myInterface MyInterface = &myStruct
	
myResult := myInterface.MyPointerReceiverMethod())

是的——我也是个新手,前几天也遇到了这个问题。

第一个问题是您将 myFunction 设为了私有。

第二个问题是,要访问带有指针接收器的接口方法,您必须:

  1. 创建结构体的具体版本。
  2. 将地址转换为接口。

我想您会发现以下代码可以工作:

package main

import "fmt"

type MyFunction struct{}

type MyInterface interface {
	Create(myData *MyData) (*MyData, error)
	Fetch(test string)
	Delete(test string)
}

type MyData struct {
	S string
}

func (a *MyFunction) Create(data *MyData) (*MyData, error) {
	return nil, nil
}

func (a *MyFunction) Fetch(test string) {

}

func (a *MyFunction) Delete(test string) {

}

func main() {
	myFunc := MyFunction{}
	var myInt MyInterface = &myFunc
	md, _ := myInt.Create(&MyData{})
	fmt.Println(md)
}

正如我所说,我是个新手,所以通常的警告也适用。如果我说的不对,希望有专家能指正。

我已经使用Go语言几年了,但我觉得我还不算任何方面的专家!

@scotpip 的回答是正确的:它向你展示了如何将一个具体的事物(即 MyFunction)"转换"成一个接口 MyInterface,然后调用其上的方法。

话虽如此,在这种情况下使用接口是有些牵强的。根据你的问题,我怀疑这只是为了初步涉足接口并了解其工作原理的一次学习体验,我无意贬低这一点;欢迎随时提出更多问题!我对scotpip的回答你的问题都没有意见,但我想履行我作为Gopher的"职责",明确说明在这种情况下,你可以直接使用你的 MyFunction,而不是先将其转换为接口。

当你并不关心你所使用的事物的确切类型,而只关心你能用它做什么时,接口是一个极好的工具。

例如,假设你希望程序输出日志消息用于调试。虽然已经有一个 log 包可以为你做这件事,但假设它不存在,而你想做一些简单的事情。你可以这样做:

// logger 持有日志消息将被写入的写入器。
var logger io.Writer = os.Stdout // 默认情况下,写入标准输出

// SetLogger 允许将程序日志输出更改为另一个目标。
func SetLogger(w io.Writer) {
    logger = w
}

// 将应用程序的消息记录到日志记录器
func log(message string, args ...interface{}) {
    fmt.Fprintf(logger, message, args...)
}

func main() {
    log("starting application...")
    num, den := 3, 0
    res := div(num, den)
    fmt.Println("%d ÷ %d = %d", num, den, res)
    log("exiting application...")
}

func div(num, den int) int {
    if den == 0 {
        log("attempt to divide %d by 0", num)
        return 0 // TODO: 返回错误而不是仅仅返回0
    }
    return num / den
}

那么,如果你不想写入控制台,而是想写入文件呢?你只需更改日志记录器:

func main() {
    // ...
    f, err := os.Create("log.txt")
    if err != nil {
        panic(err) // TODO: 在这里更好地处理错误。
    }
    defer f.Close()
    SetLogger(f)
    // ...
}

如果你的程序不关心日志消息被写入哪里,只要无论是什么,它都有一个 Write 方法,那么接口就是一个完美的选择。

你的代码有几个关键问题需要修正:

  1. 接口不能直接调用方法 - 接口是类型定义,不是可实例化的对象
  2. 需要实现接口的具体类型实例
  3. 接口变量需要被赋值具体的实现

以下是修正后的代码:

修正后的代码结构

a/models.go

package a

type MyData struct {
    // 你的字段定义
    ID   string
    Name string
}

a/a.go

package a

// 私有结构体
type myFunction struct{}

// 公共接口
type MyFunc interface {
    Create(myData *MyData) (*MyData, error)
    Fetch(test string)
    Delete(test string)
}

// 工厂函数,返回接口类型
func NewMyFunc() MyFunc {
    return &myFunction{}
}

// 接口实现
func (m *myFunction) Create(data *MyData) (*MyData, error) {
    // 实现逻辑
    return data, nil
}

func (m *myFunction) Fetch(test string) {
    // 实现逻辑
}

func (m *myFunction) Delete(test string) {
    // 实现逻辑
}

main.go

package main

import (
    "fmt"
    "your_module_path/a" // 替换为你的实际模块路径
)

func main() {
    // 1. 创建接口实例
    myFunc := a.NewMyFunc()
    
    // 2. 创建数据
    data := &a.MyData{
        ID:   "123",
        Name: "Test",
    }
    
    // 3. 通过接口调用方法
    result, err := myFunc.Create(data)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    fmt.Printf("Result: %+v\n", result)
}

替代方案:直接使用结构体

如果你不需要接口的灵活性,也可以直接导出结构体:

a/a.go(直接结构体版本)

package a

// 公共结构体
type MyFunction struct{}

func (m *MyFunction) Create(data *MyData) (*MyData, error) {
    return data, nil
}

func (m *MyFunction) Fetch(test string) {
    // 实现逻辑
}

func (m *MyFunction) Delete(test string) {
    // 实现逻辑
}

main.go(直接结构体版本)

package main

import (
    "fmt"
    "your_module_path/a"
)

func main() {
    // 直接实例化结构体
    myFunc := &a.MyFunction{}
    
    data := &a.MyData{
        ID:   "123",
        Name: "Test",
    }
    
    result, err := myFunc.Create(data)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    fmt.Printf("Result: %+v\n", result)
}

关键点说明

  1. 接口是类型,不是值a.MyFunc 是一个接口类型,不能直接调用方法
  2. 需要具体实现:必须有一个实现了接口所有方法的具体类型
  3. 接口变量赋值:接口变量必须被赋值为具体实现的实例
  4. 工厂函数模式:使用 NewMyFunc() 这样的工厂函数是常见的做法,它隐藏了具体实现类型

错误信息 “调用a.MyFunc.Create的参数太少” 是因为你试图在接口类型上直接调用方法,这在Go中是不允许的。必须先创建实现了该接口的具体类型的实例,然后通过该实例调用方法。

回到顶部