我试图理解接口的用途。我写了这个例子:
package main
type Talker interface {
Talk()
}
type Person1 struct {
text string
}
type Person2 struct {
text string
}
func (n *Person1) Talk() {
println("person1 says", n.text)
}
func (n *Person2) Talk() {
println("person2 says", n.text)
}
func Converse(l Talker) {
l.Talk()
}
func main() {
Converse(&Person1{"hello"})
Converse(&Person2{"hi"})
}
写完后,我可以这样表述该接口的用途:
如果一个对象没有实现接口的所有方法,或者实现了至少一个与接口中指定的签名不同的方法,那么该对象将无法访问该接口的对象的通用功能(在我的示例中,是 Converse(l Talker) 函数)。
接口的目的真的只是以某种方式封装对象及其数据吗?看起来这似乎是对程序员的一种保护,因为他不明白自己在做什么,也不明白处理对象和数据需要什么方法和函数,而接口则向他澄清,“你确定这个对象应该正是处理这个函数吗?” 以前,我没有以这种方式考虑接口,因为它们总是作为通用化某些东西的手段而呈现,当我寻找这种通用化时我无法理解它们,因为我不观察通用化(谈论方法,无论如何,你需要为每个对象写不同的)
我将添加到答案:
package main
import (
"log"
"os"
)
type MoveSaveReader interface {
Copy()
Save()
Read()
}
type OBJ struct {
data, filename, newpath string
}
type NewObj struct {
data, path string
}
func (o *NewObj) Copy() {
}
func (o *NewObj) Save() {
err := os.WriteFile(o.path, []byte(o.data), 6060)
if err != nil {
log.Println(err)
return
}
log.Println("Saved")
}
func (o *NewObj) Read() {
d, err := os.ReadFile(o.path)
if err != nil {
log.Println(err)
return
}
o.data = string(d)
}
func (o *OBJ) Copy() {
o.Read()
o.filename = o.newpath
o.Save()
}
func (o *OBJ) Save() {
err := os.WriteFile(o.filename, []byte(o.data), 6060)
if err != nil {
log.Println(err)
return
}
log.Println("Saved")
}
func (o *OBJ) Read() {
d, err := os.ReadFile(o.filename)
if err != nil {
log.Println(err)
return
}
o.data = string(d)
}
func CopyFile(m MoveSaveReader) {
m.Copy()
}
func WriteNewFile(m MoveSaveReader) {
m.Save()
}
func ReadFile(m MoveSaveReader) {
m.Read()
}
func main() {
obj := &OBJ{"sometext", "path", "newpath"}
WriteNewFile(obj)
ReadFile(obj)
CopyFile(obj)
newObj := &NewObj{"newtext", "newSomePAth"}
WriteNewFile(newObj)
ReadFile(newObj)
}
不具有至少一种方法 的对象MoveSaveReader
,即:Copy()
、Save()
,Read()
或者有方法但至少有一个错误的签名,则这样的对象将受到保护,不会在函数CopyFile(m MoveSaveReader)
、ReadFile(m MoveSaveReader)
、中被错误放置WriteFile(m MoveSaveReader)
。也就是说,借助接口我不再通用化数据处理,而是保护它?
简而言之,接口的主要目的是实现类型安全的鸭子类型范例。
如果更详细的话。
我不了解你,但我熟悉了 Java 语言中的接口。在那里,接口是一种只有方法签名、没有数据、没有方法体的类型。如果一个类实现了一个接口,那么它包含该接口的所有方法。例如,
Closable
只有一个方法的接口void close()
。Java 标准库包含几十个实现接口的类Closable
,每个类都有一个典型的方法close
。java 和 go 接口有什么区别,鸭子类型与它有什么关系?
在Java中,接口和类型之间的关系是从左到右:如果一个类型实现了一个接口,那么该类型就拥有该接口的所有方法。在 go 中,方向相反。如果一个类型实现了接口的所有方法,那么该类型就实现了该接口。
差异实际上是巨大的。在 Java 中,类型仅实现类开发人员列出的那些接口。如果他们没有提到某些内容,那么无论如何,您都无法将类型转换为所需的接口。
例子。我想设置一个界面
库中
java.io
有一个类Writer
,其中包含一个带有Writer.write
正确签名的方法。这是否意味着类的对象java.io.Writer
也是类型的对象Writable
?不,不是,因为该类的创建者Writer
不知道我的接口的存在Writable
,没有在 部分中指出它implements
,因此没有将其包含在对象类型列表中Writer
。在 Go 中,情况正好相反。创建类型时,没有人列出该类型实现的接口。在检查对象是否可转换为接口时,编译器会检查该类型是否具有所有必要的方法。如果有方法,则该类型实现该接口。因此,所有具有方法的类型都
func (T) Write([]byte)(int,error)
实现接口io.Writer
只是因为它们实现了所需的方法。即使他们自己也不知道这件事。看起来像鸭子,走路像鸭子,嘎嘎叫像鸭子——这意味着它是鸭子。
现在关于类型安全。
鸭子类型在解释语言中经常出现。Python、Javascript、Ruby、Lisp……数不胜数。在这些语言中的任何一种中,您都可以编写类似的内容
stream.write(buf)
(对于 Lisp 来说是(write stream buf)
),并向自己祈祷会有stream
一个方法write
可以处理 类型的对象buf
。如果你的祈祷被听到,那么一切都很好,程序可以正常工作。但如果所需的方法不存在,那么您只能在程序执行期间才能找到它。如果write
在长时间计算的最后调用这个函数,并且由于stream
错误的系统而导致所有结果毫无意义地丢失,那么这是特别烦人的。在 go 中,编译器检查对象是否具有必要的方法:名称、参数类型和返回值类型匹配。如果至少有一些不匹配,那么您将在编译阶段找到它。该程序根本无法运行。
所以,如果你写这样的东西,
func save_results(data Data, sink io.Writable) { ...; _, err := sink.Write(data.Bytes()); ... }
你可以绝对确定该对象sink
有一个名为 的方法Write
,并且这个方法[]byte
,error
。如果此代码在运行时中断,则不会是因为缺少所需的方法。