如前所述,接口是一种工具。并不是所有的包都会从中受益,但对于某些编程任务,接口对于抽象和创建包 API 非常有用,特别是对于库代码或可能以多种方式实现的代码。
以负责将一些原始图形绘制到屏幕上的包为例。我们可以认为屏幕的绝对基本基本要求是能够绘制像素、清除屏幕、定期刷新屏幕内容,以及获取有关屏幕的一些基本几何信息,例如其当前尺寸。因此,“屏幕”界面可能如下所示;
type Screen interface {
Dimensions() (w uint32, h uint32)
Origin() (x uint32, y uint32)
Clear()
Refresh()
Draw(color Color, point Point)
}
现在我们的程序可能有几个不同的“图形驱动程序”,我们的图形包可以使用它们来满足屏幕的这个基本要求。您可能正在使用一些本机操作系统驱动程序,可能是 SDL2 包,也可能是其他东西。也许在您的程序中,您需要支持多种绘制图形的选项,因为它依赖于操作系统环境等等。
因此,您可以定义三个结构,每个结构都包含操作系统/库等中底层屏幕绘制例程所需的资源;
type SDLDriver struct {
window *sdl.Window
renderer *sdl.Renderer
}
type NativeDriver struct {
someDataField *Whatever
}
type AnotherDriver struct {
someDataField *Whatever
}
然后您在代码中实现所有这三个结构的方法接口,以便这三个结构中的任何一个都可以满足屏幕接口的要求
func (s SDLDriver) Dimensions() (w uint32, h uint32) {
// implement Dimensions()
}
func (s SDLDriver) Origin() (x uint32, y uint32) {
// implement Origin()
}
func (s SDLDriver) Clear() {
// implement Clear()
}
func (s SDLDriver) Refresh() {
// implement Refresh()
}
func (s SDLDriver) Draw(color Color, point Point) {
// implement Draw()
}
...
func (s NativeDriver) Dimensions() (w uint32, h uint32) {
// implement Dimensions()
}
func (s NativeDriver) Origin() (x uint32, y uint32) {
// implement Origin()
}
func (s NativeDriver) Clear() {
// implement Clear()
}
func (s NativeDriver) Refresh() {
// implement Refresh()
}
func (s NativeDriver) Draw(color Color, point Point) {
// implement Draw()
}
... and so on
现在,您的外部程序真的不应该关心您可能正在使用哪些驱动程序,只要它可以通过标准界面清除、绘制和刷新屏幕即可。这是抽象。您在包级别提供程序其余部分运行所需的绝对最小值。只有图形内的代码需要了解操作如何工作的所有“细节”。
因此您可能知道需要为给定环境创建哪个屏幕驱动程序,这可能是在执行开始时根据检查用户系统上可用的内容来决定的。您决定 SDL2 是最佳选择并创建一个新的 SDLGraphics 实例;
sdlGraphics, err := graphics.CreateSDLGraphics(0, 0, 800, 600)
但是你现在可以从这里创建一个 Screen 的类型变量;
var screen graphics.Screen = sdlGraphics
现在您有了一个名为“screen”的通用“Screen”类型,它实现(假设您对它们进行了编程)Clear()、Draw()、Refresh()、Origin() 和 Dimensions() 方法。从此时起,在您的代码中,您可以完全自信地发出诸如
之类的语句
screen.Clear()
screen.Refresh()
等等......这样做的好处是你有一个标准类型,称为“屏幕”,你的程序的其余部分,它真的不关心图形库的内部工作,可以在没有的情况下使用它考虑一下。您可以将“屏幕”传递给任何功能等,确信它会正常工作。
接口非常有用,它们确实可以帮助您考虑代码的功能,而不是结构中的数据。而且小接口更好!
例如,与其在 Screen 界面中进行一大堆渲染操作,不如设计第二个这样的界面;
type Renderer interface {
Fill(rect Rect, color Color)
DrawLine(x float64, y float64, color Color)
... and so on
}
这肯定需要一些时间来适应,具体取决于您的编程经验和您以前使用过的语言。如果你之前一直是一个严格的 Python 程序员,你会发现 Go 完全不同,但如果你一直在使用 Java/C++,那么你会很快理解 Go。接口为您提供面向对象的特性,而没有其他语言(例如 Java)中存在的烦恼。