Menu
CoddyTech

接口即契约

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 27 课,共 107 课。

把接口看作契约,有助于你设计出更好的 Go 程序。当函数接受接口类型时,它表达的意思是:“我不在乎你给我什么具体类型,只要它满足这份契约就行。”

这种基于契约的思维方式能让你编写专注于行为而非特定类型的函数。考虑一个需要处理来自不同来源的数据的函数:

type DataSource interface {
    FetchData() string
}

func ProcessData(src DataSource) {
    data := src.FetchData()
    fmt.Println("Processing:", data)
}

ProcessData 函数不需要知道或关心它接收的是数据库连接、API 客户端还是文件读取器。它只要求输入能够调用 FetchData()。任何遵守这一约定的类型都可以。

type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }

type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }

func main() {
    db := Database{ConnectionString: "localhost"}
    api := APIClient{Endpoint: "https://api.example.com"}
    
    ProcessData(db)   // 可以运行!
    ProcessData(api)  // 也可以运行!
}

这种契约方式也让你的代码更容易测试。在测试时,你可以创建一个符合接口的模拟类型,而不需要连接真实的数据库或网络。被测试的函数不会察觉到区别,因为契约得到了满足。

challenge icon

挑战

简单

让我们构建一个通知系统,展示接口如何充当契约。你将创建一个 Notifier 接口,定义发送通知的方式,然后实现多个通知渠道,让它们都遵守这个契约。

你将把代码分布在两个文件中:

  • notifiers.go:定义一个只有一个方法 Notify(message string) string 的 Notifier 接口。然后创建三个满足此契约的不同结构体:
    • ConsoleNotifier,包含一个 Prefix 字段
    • FileNotifier,包含一个 Filename 字段
    • WebhookNotifier,包含一个 URL 字段
    每个结构体都应实现 Notify 方法,并返回一个描述通知发送方式的字符串。
  • main.go:创建一个名为 BroadcastAlert 的函数,该函数接收一个由 Notifier 值组成的切片和一个消息字符串。此函数应对每个通知器调用 Notify,并打印结果。从输入中读取配置,创建这三种通知器类型,然后通过它们全部广播一条警报。

将提供以下输入:

  • 第 1 行:控制台前缀
  • 第 2 行:文件名
  • 第 3 行:Webhook URL
  • 第 4 行:警报消息

你的 Notify 方法应按以下格式返回字符串:

  • ConsoleNotifier:[Prefix] [message]
  • FileNotifier:Writing to [Filename]: [message]
  • WebhookNotifier:POST [URL]: [message]

例如,输入 ALERT:、log.txt、https://hooks.example.com 和 Server down! 时,输出应为:

ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!

这种设计的优点是,BroadcastAlert 函数不需要知道或关心具体的通知器类型。它只要求每个通知器都遵守 Notifier 契约。之后,你可以添加 SlackNotifier 或 EmailNotifier,而完全不必更改广播函数。

亲手试试

package main

import (
	"bufio"
	"fmt"
	"os"
)

// BroadcastAlert 通过所有通知器发送消息
// TODO:实现此函数,使其:
// 接收一个由 Notifier 值组成的切片和一个消息字符串
// 对每个通知器调用 Notify 并打印结果
func BroadcastAlert(notifiers []Notifier, message string) {
	// TODO:遍历 notifiers 并打印每条通知的结果
}

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 读取控制台前缀
	scanner.Scan()
	prefix := scanner.Text()
	
	// 读取文件名
	scanner.Scan()
	filename := scanner.Text()
	
	// 读取 webhook URL
	scanner.Scan()
	url := scanner.Text()
	
	// 读取警报消息
	scanner.Scan()
	message := scanner.Text()
	
	// TODO:创建全部三种通知器类型的实例
	// 用前缀创建 ConsoleNotifier
	// 用文件名创建 FileNotifier
	// - 使用 URL 的 WebhookNotifier
	
	// TODO:创建一个包含全部三个通知器的 Notifier 切片
	
	// TODO:使用 notifiers 和 message 调用 BroadcastAlert
	
	_ = prefix
	_ = filename
	_ = url
	_ = message
	fmt.Println("TODO: Implement the solution")
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器