结合咱们之前的学习,本节将带领大家来编写一个聊天机器人的雏形。下面的代码中展示了一个很傻的聊天程序。
packagemainimport("bufio""fmt""os""strings")funcmain(){//准备从标准输入读取数据。inputReader:=bufio.NewReader(os.Stdin)fmt.Println("Pleaseinputyourname:")//读取数据直到碰到n为止。input,err:=inputReader.ReadString('n')iferr!=nil{fmt.Printf("Anerroroccurred:%sn",err)//异常退出。os.Exit(1)}else{//用切片操作删除最后的n。name:=input[:len(input)-2]fmt.Printf("Hello,%s!WhatcanIdoforyou?n",name)}for{input,err=inputReader.ReadString('n')iferr!=nil{fmt.Printf("Anerroroccurred:%sn",err)continue}input=input[:len(input)-2]//全部转换为小写。input=strings.ToLower(input)switchinput{case"":continuecase"nothing","bye":fmt.Println("Bye!")//正常退出。os.Exit(0)default:fmt.Println("Sorry,Ididn'tcatchyou.")}}}
这个聊天程序在问候用户之后会不断地询问“是否可以帮忙”,但是实际上它什么忙也帮不上。因为它现在什么也听不懂,除了nothing和bye。一看到这两个词,它就会与用户“道别”,停止运行。现在试运行一下这个命令源码文件:
D:code>goruntest.go
Pleaseinputyourname:
->Robert
Hello,Robert!WhatcanIdoforyou?
->Apieceofcake,please.
Sorry,Ididn'tcatchyou.
->Bye
Bye!
注意,其中的“->”符号之后的内容是我们输入的。让这个聊天程序聪明起来需要一些工作量,工作量的多少取决于要让它聪明到什么程度。或者我们换一种思路:让它成为可以变聪明的聊天机器人。
下面介绍两个接口分别是Chatbot接口和Talk接口,这两个接口分别定义了聊天机器人和聊天的行为。程序的用户可以指定或制定聊天机器人和聊天的行为。结构体类型simpleCN其实就是这些接口的实现。代码如下所示:
packagechatbotimport("fmt""strings")//simpleCN代表针对中文的演示级聊天机器人。typesimpleCNstruct{namestringtalkTalk}//NewSimpleCN用于创建针对中文的演示级聊天机器人。funcNewSimpleCN(namestring,talkTalk)Chatbot{return&simpleCN{name:name,talk:talk,}}//Name是Chatbot接口的实现的一部分。func(robot*simpleCN)Name()string{returnrobot.name}//Begin是Chatbot接口的实现的一部分。func(robot*simpleCN)Begin()(string,error){return"请输入你的名字:",nil}//Hello是Talk接口的实现的一部分。func(robot*simpleCN)Hello(userNamestring)string{userName=strings.TrimSpace(userName)ifrobot.talk!=nil{returnrobot.talk.Hello(userName)}returnfmt.Sprintf("你好,%s!我可以为你做些什么?",userName)}//Talk是Talk接口的实现的一部分。func(robot*simpleCN)Talk(heardstring)(sayingstring,endbool,errerror){heard=strings.TrimSpace(heard)ifrobot.talk!=nil{returnrobot.talk.Talk(heard)}switchheard{case"":returncase"没有","再见":saying="再见!"end=truereturndefault:saying="对不起,我没听懂你说的。"return}}//ReportError是Chatbot接口的实现的一部分。func(robot*simpleCN)ReportError(errerror)string{returnfmt.Sprintf("发生了一个错误:%sn",err)}//End是Chatbot接口的实现的一部分。func(robot*simpleCN)End()error{returnnil}
*simpleCN类型同时实现了Chatbot接口和Talk接口,其中的Name方法和Talk方法是为了实现Talk接口。如果发现talk字段不为nil,它们就会直接使用该字段中的同名方法,否则就会自己处理。
这使得对聊天行为的定制是可选的。simpleCN也因此做到了开箱即用。另外请注意,simpleCN是包级私有的。包外代码需要通过NewSimpleCN函数创建它的实例值,这实际上也属于Go的惯用法。
simpleCN其实实现了一个中文聊天机器人的模板。在使用时自定义聊天行为即可。不过,聊天程序依然应该允许用户注册完全自定义的聊天机器人。请看下面的代码:
//chatbotMap代表了名称-聊天机器人的映射。varchatbotMap=map[string]Chatbot{}//Register用于注册聊天机器人。//若结果值为nil则表示注册成功。funcRegister(chatbotChatbot)error{ifchatbot==nil{returnErrInvalidChatbot}name:=chatbot.Name()ifname==""{returnErrInvalidChatbotName}if_,ok:=chatbotMap[name];ok{returnErrExistingChatbot}chatbotMap[name]=chatbotreturnnil}//Get用于获取指定名称的聊天机器人。funcGet(namestring)Chatbot{returnchatbotMap[name]}
这提供了预先配置聊天机器人实例的能力。
在聊天程序的入口文件main.go中,首先需要提供定制聊天机器人的途径:
//chatbotName决定了对话使用的聊天机器人。varchatbotNamestringfuncinit(){flag.StringVar(&chatbotName,"chatbot","simple.en","Thechatbot'snamefordialogue.")}
其中flag是一个标准代码包,它允许用户在启动程序时,通过特殊的命令行参数(在Go中称为标记)传入定制化信息。如果要这样的信息起作用,最好在main函数的开始处就调用flag.Parse函数。只有这样,命令行中的标记值才能与chatbotName绑定,然后才可以把它传入Get函数,从而拿到相应的聊天机器人实例。
这个聊天程序中包含了本教程前面讲述的Go语法的大部分用法,大家可以用它来熟悉Go程序的基本写法。下面是一个使用示例:
PSD:code>gorun.main.go-chatbotsimple.cn
请输入你的名字:
C语言中文网
你好,C语言中文网!我可以为你做些什么?
请给我一本Go语言教程
对不起,我没听懂你说的。
再见
再见!
从表面上看,这个聊天机器人依然很傻。不过这表现的只是默认行为,是否让它变聪明完全取决于使用者的定制。