构建一个待办事项应用的第一步是确立数据模型。在Golang中,结构体是封装数据的理想选择。我们需要定义一个结构体来表示每一个待办任务,其中应当包含任务的唯一标识符、任务的具体内容以及任务当前的完成状态。唯一标识符可以使用整数类型,任务内容使用字符串,而完成状态则用布尔类型来表示。

设计待办事项的数据结构与存储方案
为了在内存中管理这些任务,我们通常会使用一个切片来存储结构体指针或者结构体本身。使用切片的好处在于其长度是动态可变的,非常适合待办事项数量不固定的场景。同时,切片在Go语言标准库中拥有丰富的操作支持,能够方便地进行追加和删除操作。通过将任务统一放入切片中,我们可以很轻松地遍历整个列表来展示未完成的任务。
在设计存储方案时,除了定义任务本身,还需要一个管理器结构体来统筹全局。这个管理器将持有任务切片并维护一个自增的ID计数器。这种设计模式在业务开发中非常常见,它将状态与行为封装在一起,使得代码逻辑更加内聚。下面我们通过代码来展示如何定义这些核心结构。
package main
import (
"fmt"
"strconv"
"strings"
"bufio"
"os"
)
// Task 定义待办事项的数据结构
type Task struct {
ID int
Content string
Completed bool
}
// TodoList 管理所有任务的管理器
type TodoList struct {
tasks []Task
nextID int
}
// NewTodoList 初始化一个新的待办列表
func NewTodoList() *TodoList {
return &TodoList{
tasks: []Task{},
nextID: 1,
}
}
在上述代码中,我们定义了Task结构体以及一个TodoList结构体来管理任务集合。TodoList内部包含一个Task切片,并且内置了一个自增的ID计数器,这样每次添加新任务时都能确保获得一个不重复的编号,避免了使用随机字符串或者复杂算法生成ID的麻烦。这种基于内存的存储方案虽然无法在程序重启后保留数据,但对于学习业务逻辑和命令行交互已经足够。
实现任务的添加与删除逻辑
有了数据结构的基础,接下来就可以实现核心的业务逻辑。添加操作相对简单,只需要将新创建的Task追加到切片末尾即可。但在删除操作时,情况会稍微复杂一些。由于Go语言的切片底层是一段连续的内存,删除中间的元素需要将后续元素向前移动,或者利用切片的特性重新拼接。
在实现删除逻辑时,我们通常通过遍历切片找到对应ID的索引位置。一旦找到目标索引,就可以利用切片的append特性,将目标元素之前的部分和之后的部分重新拼接起来,从而跳过需要删除的元素。这种方法不仅代码简洁,而且执行效率也相当不错。下面是添加和删除功能的具体实现代码。
// Add 添加一个新的待办事项
func (tl *TodoList) Add(content string) {
task := Task{
ID: tl.nextID,
Content: content,
Completed: false,
}
tl.tasks = append(tl.tasks, task)
tl.nextID++
fmt.Printf("成功添加任务: %s\n", content)
}
// Delete 根据ID删除待办事项
func (tl *TodoList) Delete(id int) error {
index := -1
for i, t := range tl.tasks {
if t.ID == id {
index = i
break
}
}
if index == -1 {
return fmt.Errorf("未找到ID为 %d 的任务", id)
}
// 利用切片特性删除指定索引的元素
tl.tasks = append(tl.tasks[:index], tl.tasks[index+1:]...)
fmt.Printf("成功删除任务 ID: %d\n", id)
return nil
}
上述代码展示了添加和删除的具体实现。Add方法递增ID并构建新任务,随后将其追加到列表中。Delete方法通过for循环遍历查找索引,如果未找到则返回错误提示。找到后利用切片特性完成删除。这种基于内存的增删操作响应速度极快,能够满足日常命令行工具的交互需求。在实际生产环境中,如果数据量巨大,可以考虑引入基于哈希的映射来优化查找效率。
标记任务完成与交互式命令行构建
待办事项应用的核心在于状态流转,即标记任务为已完成。这需要我们提供一种更新机制,允许用户输入任务ID,然后系统将对应任务的完成状态字段修改为真。在Go语言中,由于我们操作的是切片,直接修改切片元素的字段需要获取其指针或者通过索引直接访问。
实现完成操作时,同样需要遍历切片查找指定ID的任务。如果任务不存在,应当返回明确的错误信息,以便在命令行界面给用户友好的反馈。找到任务后,直接修改其Completed字段即可。这种设计使得状态管理变得非常直观。下面展示了状态更新和列表打印的逻辑。
// Complete 标记任务为已完成
func (tl *TodoList) Complete(id int) error {
for i, t := range tl.tasks {
if t.ID == id {
tl.tasks[i].Completed = true
fmt.Printf("成功完成任务 ID: %d\n", id)
return nil
}
}
return fmt.Errorf("未找到ID为 %d 的任务", id)
}
// List 展示所有待办事项
func (tl *TodoList) List() {
if len(tl.tasks) == 0 {
fmt.Println("当前没有待办任务")
return
}
fmt.Println("========== 待办事项列表 ==========")
for _, t := range tl.tasks {
status := "未完成"
if t.Completed {
status = "已完成"
}
fmt.Printf("ID: %d | 状态: %s | 内容: %s\n", t.ID, status, t.Content)
}
fmt.Println("===================================")
}
最后,我们需要将这些业务逻辑串联起来,构建一个可交互的命令行入口。可以利用标准库中的bufio包来读取用户输入,通过strings包解析命令和参数。当用户输入add后跟任务内容时,调用Add方法;输入done后跟ID时,调用Complete方法;输入delete时调用Delete方法。这样,一个完整的待办事项应用就具备了基本的可用性。下面是主函数的交互逻辑实现。
func main() {
todoList := NewTodoList()
reader := bufio.NewReader(os.Stdin)
fmt.Println("欢迎使用待办事项应用,可用命令: add <任务>, done <ID>, delete <ID>, list, exit")
for {
fmt.Print("> ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "exit" {
fmt.Println("退出应用")
break
}
parts := strings.SplitN(input, " ", 2)
cmd := parts[0]
switch cmd {
case "add":
if len(parts) < 2 {
fmt.Println("请输入任务内容")
continue
}
todoList.Add(parts[1])
case "done":
if len(parts) < 2 {
fmt.Println("请输入任务ID")
continue
}
id, err := strconv.Atoi(parts[1])
if err != nil {
fmt.Println("ID必须是数字")
continue
}
if err := todoList.Complete(id); err != nil {
fmt.Println(err)
}
case "delete":
if len(parts) < 2 {
fmt.Println("请输入任务ID")
continue
}
id, err := strconv.Atoi(parts[1])
if err != nil {
fmt.Println("ID必须是数字")
continue
}
if err := todoList.Delete(id); err != nil {
fmt.Println(err)
}
case "list":
todoList.List()
default:
fmt.Println("未知命令")
}
}
}
通过上述主函数,我们利用一个无限循环不断读取用户的控制台输入,并使用switch语句对不同的命令进行分发处理。这种基于事件循环的架构是命令行工具最基础的设计模式。当用户输入exit时,循环跳出,程序正常退出。整个应用逻辑清晰,结构紧凑,充分体现了Golang在编写命令行工具时的简洁与高效。你可以在此基础上进一步扩展,比如引入JSON文件来实现数据的本地持久化,或者增加任务优先级等功能。