在Golang中,返回接口的时间和原因是什么?

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

When and why to return an interface in Golang?

问题

示例代码:

type IClient interface {
    UploadFile(sourcePath, host string) error
    CopyFile(sourcePath, destPath string) error
    DeleteFile(sourcePath, option string) error
    GetChecksum(sourcePath string) (*string, error)
    GetSize(sourcePath string) (*float32, error)
}

type FileWorker struct {
    Extension string
    Host      string
}

func NewFileWorker(fileExtension, host string) IClient {
    var fileWorker = &FileWorker{
        Extension: fileExtension,
        Host:      host,
    }
    return fileWorker
}

//注意:FileWorker类型是实现IClient接口的所有方法的接收者

我看到过其他类似上述代码中返回接口的“New”(或“Get”)函数。但实际上它并不返回接口,而是返回实现该接口的结构体。而这个结构体类型是实现该接口的各种方法的接收者。

问题:我们何时以及为什么返回接口?目前我看到这是一种绕弯的方式,导致在尝试理解某些代码时需要追溯多个文件。

英文:

Example code:

type IClient interface {
	UploadFile(sourcePath, host string) error
	CopyFile(sourcePath, destPath string) error
	DeleteFile(sourcePath, option string) error
	GetChecksum(sourcePath string) (*string, error)
	GetSize(sourcePath string) (*float32, error)
}

type FileWorker struct {
    Extension string
    Host string
}

func NewFileWorker(fileExtension, host string) IClient {
	var fileWorker = &FileWorker {
		Extension: extension,
		Host: host,
	}
	return fileWorker
}

//NOTE: type FileWorker is the receiver of all methods implementing the IClient interface

I have seen other "New" (or "Get") function returning interface like above. But it does not actually return the interface, but instead a struct that implements said interface. In turn, that struct type is the receiver of various methods implementing said interface.

Question: When and why do we return interfaces? Currently I see this is a roundabout way of doing things, resulting in one having to trace back through multiple files while trying to understand some codes.

答案1

得分: 1

接口是实现代码重用的一种方式。通常情况下,重要的不是对象是什么,而是它能做什么。考虑以下方法:

func Grade(questions []MultipleChoiceQuestion) {
	total := len(questions)
	correct := 0
    for _, q := range questions {
		if q.Test() {
			correct++
		}
	}
	return correct / total
}

函数Grade接收一个问题集合并对用户的答案进行评分。

如果我们想要添加判断题怎么办?目前Grade只接受MultipleChoiceQuestion,这意味着我们需要创建一个全新的函数,比如GradeTrueOrFalse(questions []TrueOrFalseQuestions),才能进行包含判断题的测试。此外,我们将无法同时包含多项选择题和判断题。

然而,我们应该能够在单个测试中组合不同类型的问题。原始函数并不关心问题的类型,只关心问题能否进行Test()操作。这就是接口的用武之地。

通过将Test方法抽象为一个接口,我们可以在Grade中使用任何类型的问题。

type Tester interface {
	Test() bool
}

type MultipleChoiceQuestion struct

func (q MultipleChoiceQuestion) Test() bool {
    // 实现...
}

type TrueOrFalseQuestion struct

func (q TrueOrFalseQuestion) Test() bool {
    // 实现...
}

func Grade(questions []Tester) {
	total := len(questions)
	correct := 0
    for _, q := range questions {
		if q.Test() {
			correct++
		}
	}
	return correct / total
}

现在,Grade可以接收任何类型的问题。此外,questions []Tester可以包含多项选择题和判断题的混合。

当正确使用时,你很少需要执行类型断言或者"追踪多个文件"来理解发生了什么。

英文:

Interfaces are a way to achieve code reuse. Often times, what matters is not what an object is, but what it does. Consider the following method:

func Grade(questions []MultipleChoiceQuestion) {
	total := len(questions)
	correct := 0
    for _, q := range questions {
		if q.Test() {
			correct++
		}
	}
	return correct / total
}

The function Grade takes in a collection of questions and grades the user's answers.

What if we wanted to add true or false questions? Right now Grade only accepts MultipleChoiceQuestions, which means that we would have to make a whole new function, say GradeTrueOrFalse(questions []TrueOrFalseQuestions) in order to have a test with true or false questions. Furthermore, we would be unable to have a test composed of both multiple choice and true or false questions.

However, we should be able to copmbine questions in a single test. The original function doesn't care about what kind of question it is, only that the question can Test() the user's answer. This is where interfaces come in.

By abstracting away the Test method into an interface we can use Grade for any kind of question.

type Tester interface {
	Test() bool
}

type MultipleChoiceQuestion struct

func (q MultipleChoiceQuestion) Test() bool {
    // implementation...
}

type TrueOrFalseQuestion struct

func (q TrueOrFalseQuestion) Test() bool {
    // implementation...
}

func Grade(questions []Tester) {
	total := len(questions)
	correct := 0
    for _, q := range questions {
		if q.Test() {
			correct++
		}
	}
	return correct / total
}

Now Grade can take in any kind of question. Furthermore, questions []Tester can be a mix of multiple choice and true or false questions.

When used properly, you should rarely have to perform a type assertion or "trace back through multiple files" to understand what is going on.

huangapple
  • 本文由 发表于 2022年3月10日 11:08:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/71418380.html
匿名

发表评论

匿名网友

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

确定