方法链式调用
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 81 课,共 107 课。
方法链式调用是一种方法返回接收者的技术,可以将多个方法调用链接在同一条语句中。你已经在 Builder 模式中见过这种写法,但它也是一个独立的概念,在许多场景中都很有用。
关键在于从每个方法返回指向该结构体的指针:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}现在你可以流畅地将这些调用串联起来:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)与分开的语句相比,这几乎就像自然语言一样。方法链在操作用于配置或修改对象,而不需要返回其他值时效果最好。它常用于查询构建器、配置 API 和测试断言库。
请记住,每个方法都必须使用指针接收者,并返回 *StructType,这样链式调用才能正常工作。
挑战
简单让我们来构建一个流畅的 HTML 元素构建器,通过方法链式调用来创建带有属性和内容的 HTML 标签!这是方法链式调用的一种实际应用:你将创建一个 API,在构建 HTML 元素时,代码读起来几乎就像自然语言一样。
你将把代码分成两个文件:
element.go:定义带有可链式调用方法的 HTML 元素构建器。创建一个
Element结构体,用于存储标签名、属性映射(字符串到字符串)和内部内容。编写以下可链式调用的方法,每个方法都返回*Element:NewElement(tag string):构造函数,根据给定的标签名创建一个新的 Element,并初始化一个空的属性映射Attr(key, value string):为元素添加一个属性ID(id string):便捷方法,用于设置 "id" 属性Class(class string):便捷方法,用于设置 "class" 属性Content(text string):设置元素的内部内容
添加一个
Render()方法,用于返回 HTML 字符串。属性应按键名的字母顺序排列。格式如下:<tag attr1="value1" attr2="value2">content</tag>如果没有属性,只需渲染
<tag>content</tag>。如果没有内容,则渲染<tag attr="value"></tag>。main.go:使用方法链式调用构建 HTML 元素。读取标签名,然后读取要执行的操作数量。每个操作属于以下类型之一:
id,后面跟着 ID 值class,后面跟着类名attr,后面跟着属性名和值(两行)content,后面跟着内容文本
将所有操作流畅地串联起来,然后输出渲染后的 HTML。
输入内容如下:
- 第 1 行:标签名
- 第 2 行:操作数量
- 后续各行:操作类型和值
例如,给定以下输入:
div
3
id
container
class
main-content
content
Hello World输出应为:
<div class="main-content" id="container">Hello World</div>给定以下输入:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click here输出应为:
<a href="https://example.com" target="_blank">Click here</a>给定以下输入:
input
2
attr
type
text
attr
placeholder
Enter name输出应为:
<input placeholder="Enter name" type="text"></input>给定以下输入:
p
1
content
Simple paragraph输出应为:
<p>Simple paragraph</p>注意,方法链式调用能让你以清晰易读的方式逐步构建元素。每个方法都会修改元素并返回它,因此可以直接链式调用下一个方法。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 读取标签名
scanner.Scan()
tagName := scanner.Text()
// 读取操作次数
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// 创建元素
element := NewElement(tagName)
// 处理每个操作
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: 根据操作类型,链式调用对应的方法
// 操作可以是:"id"、"class"、"attr" 或 "content"
// 对于 "id":读取下一行作为 ID 值
// 对于 "class":读取下一行作为类的值
// 对于 "attr":读取接下来的 TWO 行(先读取属性名,再读取值)
// 对于 "content":读取下一行作为内容文本
// 使用方法链式调用来构建元素
_ = operation // 实现时删除这一行
}
// 输出渲染后的 HTML
fmt.Println(element.Render())
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器