个人文章分享 - 2013年2月
type: channel
link: https://pub.ofcrab.com/2013/02/
description: 芝兰生于深林，不以无人而不芳；君子修道立德，不谓穷困而改节。
items: 如何基于Go创建数据库驱动的Web应用
items: Go 模板集
items: Go 模板——结构与数据控制
items: Go 模板
items: Go 网络编程的 Hello World程序
items: Go 中的 Channels —— range 与 select
items: Go 中的 Channels
items: Go 中的 Goroutines
items: Go 的多态
items: Go 的接口 —— 进化与高可用性的设计

如何基于Go创建数据库驱动的Web应用
type: item
date: 2013-02-27T23:04:48+00:00
description: 本文原文为How to write database-driven Web application using Go的 README.md 文件，如果您想查看本文的原文，请点击前面的英文原文标题，找到该项目的 README.md 文件即可，格式为 *MarkDown*，如果你需要HTML版本的，可能还需要自己安装MarkDown相关的工具，本示例的 Github 地址为：https://github.com/pantao/example-go-wiki/本文将尝试前着去介绍如何使用 kview/kasia.go 以及 MyMySQL 基于Go语言开发一个小型的简单的数据库驱动的Web应用，就像大家做的一样，我们来开发一个小型的维基系统。对您个人的要求 一些程序开发经验； 关于 HTML 与 HTTP 的基础知识； 了解MySQL 以及 MySQL 命令行工具的使用； 在MySQL中创建数据库； 最新的 Go 编译器 － 移步 Go 首页 以了解更多详情。 数据库让我们从创建应用所使用的数据库开始该项目。如果你还没有安装MySQL，那么需要你首先安装它，接下来我们会用到它，如果你已经安装了，那么首先我们先来创建该应用数据库。cox@CoxStation:~$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 120 Server version: 5.5.28-0ubuntu0.12.04.2 (Ubuntu) Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. mysql> create database go_wiki; Query OK, 1 row affected (0.00 sec) mysql> use go_wiki; Database changed mysql> CREATE TABLE articles ( -> id INT AUTO_INCREMENT PRIMARY KEY, -> title VARCHAR(80) NOT NULL, -> body TEXT NOT NULL -> ) DEFAULT CHARSET=utf8; Query OK, 0 rows affected (0.09 sec) mysql> exit Bye到现在为止你已经创建了一个可能使用的数据库，同时还添加了一张表 articles*用来保存我们的维基中的文章数据，你可以在应用中直接使用 *root 帐户连接数据库，当然，更好的办法是创建一个独立的用来测试我们应用用户：mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON articles TO go_wiki@localhost; Query OK, 0 rows affected (0.00 sec) mysql> SET PASSWORD FOR go_wiki@localhost = PASSWORD('go_wiki'); Query OK, 0 rows affected (0.00 sec)现在我们记下刚才所获得的数据： 数据库地址：localhost 数据库名称：go_wiki 数据库用户：go_wiki 数据库密码：go_wiki 视图现在我们开始写点儿 Go 代码了，像以前一样，创建一个工作空间（如果你还不知道如何创建工作空间，请阅读我以前的文章《如何写 Go 程序》），我在这里就简单的将创建的流程所运行的命令复制到这里：cox@CoxStation:~$ mkdir go_wiki cox@CoxStation:~$ cd go_wiki cox@CoxStation:~/go_wiki$ export GOPATH=$HOME/go_wiki cox@CoxStation:~/go_wiki$ mkdir bin src pkg cox@CoxStation:~/go_wiki$ export PATH=$P…

Go 模板集
type: item
date: 2013-02-26T23:03:59+00:00
description: 模板集是Go中一种特殊的数据类型，它允许你将若干有关联性的模板归为一个组，当然了，它不是一个个体的数据类型，而是被归入了模板的数据结构中。所以，如果你在一些文件中使用了{{define}}…{{end}} 声明，那么这一块就成为了一个模板。比如一个网页，有头部（Header），主体（Body）以及页脚（Footer），那么这些区域我们都可以在一个文件中定义为一个一个的模板，它们将可以通过一次请求读取到你的程序中，例如：{{define "header"}} {{end}} {{define "body"}} {{end}} {{define "footer"}} {{end}}这里的关键知识是： 每一个模板都是由 {{define}}…{{end}} 对定义的 每一个模板都给定了一个唯一的名称——如果你在一个文件中定义重复使用一个名称将引起一个 Panic 不允许将任何字符写在 {{define}}…{{end}}对外——否则会引起Panic 这上面这样的文件被解析为一个模板集之后，Go自动的创建一个以模板名称为键，模板内容为值的 Map：tmplVar["header"] = pointer to parsed template of text " … " tmplVar["body"] = pointer to parsed template of text " … " tmplVar["footer"] = pointer to parsed template of text " "同一个模板集中的模板是知道有其它模板存在的，所以如果有一个模板是可以在各个其它模板中调用的，那么你需要将这个模板单独保存为一个文件，然后再在需要的地方调用它。现在我们来使用一个简单的示例更好的说明这一切： 定义一个模板——使用 {{define}}…{{end}} 在一个模板中引入另一个模板——使用 {{template “template name”“}} 解析多个文件成为一个模板集——使用 template.ParseFiles 执行或者转换模板——使用 template.ExecuteTemplate 先创建两人个模板文件： 模板文件一：t1.tmpl {{define "t_ab"}}a b{{template "t_cd"}}e f {{end}} 上面这个文件将被解析为名为 t_ab 的模板，它还引入了一个名为 t_cd 的模板 模板文件二：t2.tmpl {{define "t_cd"}} c d {{end}} 上面这个文件将被解析为 t_cd 模板 程序代码：package main import ( "text/template" "os" "fmt" ) func main() { fmt.Println("Load a set of templates with {{define}} clauses and execute:") s1, _ := template.ParseFiles("t1.tmpl", "t2.tmpl") //create a set of templates from many files. //Note that t1.tmpl is the file with contents "{{define "t_ab"}}a b{{template "t_cd"}}e f {{end}}" //Note that t2.tmpl is the file with contents "{{define "t_cd"}} c d {{end}}" s1.ExecuteTemplate(os.Stdout, "t_cd", nil) //just printing of c d fmt.Println() s1.ExecuteTemplate(os.Stdout, "t_ab", nil) //execute t_ab which will include t_cd fmt.Println() s1.Execute(os.Stdout, nil) //since templates in this data structure are named, there is no default template and so it prints nothing }输出结果为：c d a b c d e ftemplate.ParseGlob 与 template.ParseFiles 类似，只是它只需要接收一个通配符*作为参数，比如上面的这个示例，你可以使用 *template.ParseGlob("t.tmpl”)*也可以得到同样的结果。

Go 模板——结构与数据控制
type: item
date: 2013-02-24T23:03:01+00:00
description: http://golang.org/pkg/text/template/ 与 http://golang.org/pkg/html/template/ 是Go官方的关于 *template*包的文档，——html子包提供了一些附加的安装相关的功能，比如防止代码注入的功能经常在我们的网络编程中被使用。管道（Pipeline）Unix 用户肯定是明白什么是 Piping 数据，很多程序都提供字符输出——一个字条流，比如你在终端中键入 ls 命令，你将得到一个包含所有当前目录文件名称的列表，它可以被翻译为：“获取当前目录中所有文件名称的列表，然后通过管道将其传送到标准输出，而当前标准输出为命令行终端的屏幕”。在Unix命令行终端中，管道“*|*”就可以将你一个命令的输入传送给另一个命令，比如：cox@Cox:~$ ls | grep "a" access_log examples.desktop goworkspace VirtualBox VMs workspace上面的命令 ls 首先获取当前目录下所有文件的列表，然后再通过管道传递给 grep 命令，grep 从里面搜索包含字符“a”的条目，然后再将过滤后的结果通过管道传递给标准输出，标准输出打印出所有结果，如果你还想再过滤，还可以这样：cox@Cox:~$ ls | grep "a" | grep "V" VirtualBox VMs在Go中，我们也可以将任何一个像上面这样的字符流称之为管道，它们同样可以通过管道传送给其它命令，在下面的示例中，处于 {{ }} 中间的字符串与外面的字符串是不一样的——这些静态文本被无修改的复制到外面。package main import ( "text/template" "os" ) func main() { t := template.New("Template Test") t = template.Must(t.Parse("This is just static text. n{{"This is pipeline data - because it is evaluated within the double braces."}} {{ `So is this, but within reverse quotes.`}}n")) t.Execute(os.Stdout, nil) }输出为：This is just static text. This is pipeline data - because it is evaluated within the double braces. So is this, but within reverse quotes.模板中的 if-else-end在模板中使用 if-else 控制结构与在普通代码中使用差不多，在Go模板中，如果传送数据到if的管道为空，则等同为 false：package main import ( "os" "text/template" ) func main() { tEmpty := template.New("template test") tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} Will not print. {{ end}}n")) tEmpty.Execute(os.Stdout, nil) tWithValue := template.New("template test") tWithValue = template.Must(tWithValue.Parse("Non empty pipline if demo: {{if `anything`}} Print If Part.{{ end}}n")) tWithValue.Execute(os.Stdout, nil) tIfElse := template.New("template test") tIfElse = template.Must(tIfElse.Parse("If-else demo:{{if `anything`}} Print IF Part. {{ else }} Else part. {{end}}n")) tIfElse.Execute(os.Stdout, nil) }输出为：Empty pipeline if demo: Non empty pipline if demo: Print If Part. If-else demo: Print IF Part.点（*.*）在Go模板中，点（.）被用来引用当前的管道，你可以想像成为这个点是 Java 中的 thi…

Go 模板
type: item
date: 2013-02-22T23:02:09+00:00
description: 网络服务总是以HTML网页或者数据的形式回复客户的请求，这通常都带有很多的内容，这包括用户所请求的数据或者HTML结构，使用模板能让我们更方便的将普通的纯文本内容转换为有着特殊格式的文本，比如下面这种情况：Template: +---------------+ | Hello, {NAME} |-----+ +---------------+ | +-------------+ Data: +---> + Hello, Mary | +---------------+ | +-------------+ | NAME = "Mary" |-----+ +---------------+在Go中，我们使用 *templates*包中的如 *Parse*、*ParseFile*、*Execute*等方法从一段字符串或者一个模板文件加载模板然后转换为输出，这些用来添加到模板中的数据一些都保存在某个可以被导入的类型字段中，比如上面这个示例可以是这样的实现：+--------------------+ |template: | |"Hello, {NAME}" |-----+ +--------------------+ -------+ +-------------+ template.Execute() +---> + Hello, Mary | +--------------------+ /-------+ +-------------+ |type struct: |-----+ |Person{NAME:"Mary"} | +--------------------+最典型的使用方法是为网页生成HTML代码的文件，我们需要选择打开一个已经定义好报模板文件，然后使用*template.Execute*方法将一些数据填充到模板文件中，接着使用 *http.ResponseWriter*将结果发入到返回给客户端的数据流。func handler(w http.ResponseWriter, r *http.Request) { t := template.New("some template") // 创建一个新的 template t, _ = t.ParseFiles("tmpl/welcome.html", nil) // 从文本文件打开并分析一个模板 user := GetCurrentlyLoggedInUser() // 一个用来获取当前登陆用户信息的文法 t.Execute(w, User) } 对于你真实的网站开始，你可能需要使用 *template.ParseFiles*，但是在这里，我们做为示例，就不再单独写一个模板文件了，而是直接使用 *template.Parse*，它与前者有一样的功能，只是它不是从一个文件中读取模板，而是直接从一个字符串中创建模板。 我们不将示例写作一个Web服务，而是直接将结果打开到我们的命令行终端中。这便得我们需要使用 *io.Stdout*，它关联到我们的标准输出——os.Stdout实现了io.Writer接口。 字段替换——{{.FieldName}}要在模板文件中输出字段一数据，只需要在该字段名称前面加一个小数点“.*”，然后再将其放入一个双层的大括号“{{}}”里面即可，比如我们需要输出的字段为 *Name*，那么就只需要把 *{{.Name}} 放在模板文件的合适的位置即可。package main import ( "os" "text/template" ) type Person struct { Name string // 可导出的字段 } func main() { t := template.New("Hello Template") // 创建一个名为 Hello Template 的模板 t, _ = t.Parse("Hello {{.Name}}") // 分析模板并创建一个模板 p := Person{Name:"Mary"} // 定义一个实例 t.Execute(os.Stdout, p) // 转换模板t，填充了p中的数据 }输出为：Hello Mary为了完整演示，我在这模板里面添加一个不存在的字段nonExportedAgeField*，它是以小写字母开头的，所以未导出，这会导致一个错误，你可以从 *Execute 方法的返回结果中获取到错误信息。package main import ( "os" "text/template" "fmt" ) type Person struct { Name string nonExportedAgeField string } func main() { p := Person{Name: "Ma…

Go 网络编程的 Hello World程序
type: item
date: 2013-02-19T23:01:20+00:00
description: 如果你已经对我以前的辅导教程都有过学习或者有这样的知识，那么你应该可以开始基于Go语言的Web编程了。网络服务总是在等待用户电脑的连接、请求，并根据用户的请求进行一些处理之后返回用户需要的数据给用户，用户通过URL地址访问网络服务，每一个URL都是唯一的，它代表着网络上的某一个资源，比如你现在看到本文，它的URL地址就对应着这篇文章，不会是别的。我们的Go语言Web编程的Hello World程序，将运行在本地电脑上，并且会监听9999端口，也就是说，当你打开浏览器，在地址栏里面输入 http://localhost:9999 的时候，就可以访问到我们的这个程序。package main import ( "net/http" "fmt" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Println("Inside hanler") fmt.Fprintf(w, "Hello World from my Go Program!") } func main() { http.HandleFunc("/", handler) // 生定向所有URL到 handler 函数 http.ListenAndServe("localhost:9999", nil) // 监听来自9999端口的连接请求 }运行它之后，程序将不会有任何显示，但是你可以打开浏览器，在地址栏中输入http://localhost:9999，可以在网页中看到“Hello World from my Go Program!”，这表示你的Web程序已经开始服务了，而这个时候你返回去看运行它的终端，每一次对网页的访问，都会打印出几个“Inside hanler”字符。现在让我们来分析一下下我们在代码都具体做了什么： 像任何其它的Go程序一样，要想让程序可以执行，那么必须有一个 main 函数 为了在终端打印一些信息，所以我们像其它的示例一样，导入了fmt 包 我们导入了与Web http协议相关的包 http*，所有与该包相关的函数我们都以 *http.FuncName的形式调用。 在 main 函数中，我们将所有的请求都转发给 handler 函数，接着我们呼叫 http.HandleFunc 方法，并且传递了两人个参数，第一个参数为请求的URL地址，第二个为处理该URL请求的函数。 我们使用 http.ListenAndServer() 方法打开服务程序，并且监听了 localhost:9999 当我们打开浏览器访问 *http://localhost:9999*的时候，程序返回了“Hello World from my Go Program!”字符串。 从 http.Request 你可以获取关于当次请求的所有细节，比如URL地址，或者用户输入的数据等。 你应该使用接收到的 http.ResponseWriter 指针来发送需要返回给客户的内容。 代码 *http.HandleFunc(“/”, handler) 表示所有针对该地址的请求都将转发给 *handler*函数 现在让我们对上面的示例程序稍稍地扩展一下，让它可以接收一些用户的数据，然后将用户输入的数据展示给用户：package main import ( "net/http" "fmt" "strings" ) func helloHandler(w http.ResponseWriter, r *http.Request) { remPartOfURL := r.URL.Path[len("/hello/"):] //获取 URL 地址中 /hello/ 部分之后的所有内容 fmt.Fprintf(w, "Hello %s", remPartOfURL) } func shouthelloHandler(w http.ResponseWriter, r *http.Request) { remPartOfURL := r.URL.Path[len("/shouthello/"):] // 同上 fmt.Fprintf(w, "Hello %s!", strings.ToUpper(remPartOfURL)) } func main() { http.HandleFunc("/hello/", helloHandler) http.HandleFunc("/shouthello/", shouthelloHandler) http.ListenAndServe("localhost:9999", nil) }现在，运行该程序，然后访问下面这些地址看看： http://localhost:9999/hello/Cox http:…

Go 中的 Channels —— range 与 select
type: item
date: 2013-02-18T22:59:45+00:00
description: 本文是Go 中的 Channels的下半部分，如果你还没有阅读过第一部分，建议你先阅读它。这里我们还有一个问题，那就是数据的接收者无法知道什么时候应该停止等待数据，是否还有更多新数据或者所有数据都已经接收到了？我们应该继续等待还是可以进行下一步的工作了？一种解决办法是不断的循环查询数据来源是否已经关闭，如果Channel已经关闭，则知道数据已经全部接收完成了，但是这并不十分有效。Go提供了一个关键字 *range*，它可以帮助我们一直监听Channel直到它关闭。package main import ( "fmt" "time" "strconv" ) func makeCakeAndSend(cs chan string, count int) { for i := 1; i <= count; i++ { cakeName := "Strawberry Cake " + strconv.Itoa(i) cs <- cakeName // 传递一个 cake } } func receiveCakeAndPack(cs chan string) { for s := range cs { fmt.Println("Packing received cake: ", s) } } func main() { cs := make(chan string) go makeCakeAndSend(cs, 5) go receiveCakeAndPack(cs) du,_ := time.ParseDuration("3s") time.Sleep(du) }输出为：Packing received cake: Strawberry Cake 1 Packing received cake: Strawberry Cake 2 Packing received cake: Strawberry Cake 3 Packing received cake: Strawberry Cake 4 Packing received cake: Strawberry Cake 5对于 makeCakeAndSend*来说，我们已经指定了要制作的蛋糕数，但是对于 ＊receiveCakeAndPack*来说，它事先并不知道将有多少个蛋糕需要它打包，在上一个示例中，我们将这个蛋糕数硬编码至代码中，所以，它知道什么时候完成，但是现在我们改用了 *range 这个关键字，现在当 channel 关闭之后，循环会自动退出。Channel 与 select我们还有一种情况，那就是多台生产蛋糕的电脑可以通过多个传送带向同一个打包的电脑传送蛋糕，因为理论上讲蛋糕生产速度是没有打包速度快的，所以，我现在两人台电脑同时生产蛋糕，然后配备一台电脑用来打包，这个时候我们可以使用一个传送带（一个Channel）:package main import ( "fmt" "time" "strconv" ) func makeCakeAndSend(cs chan string, flavor string, count int) { for i := 1; i <= count; i++ { cakeName := flavor + " Cake " + strconv.Itoa(i) cs <- cakeName // 传递一个 cake } } func receiveCakeAndPack(cs chan string) { for s := range cs { fmt.Println("Packing received cake: ", s) } } func main() { cs := make(chan string) go makeCakeAndSend(cs, "Strawberry", 5) go makeCakeAndSend(cs, "Chocolate", 5) go receiveCakeAndPack(cs) du,_ := time.ParseDuration("2s") time.Sleep(du) }运行结果为：Packing received cake: Strawberry Cake 1 Packing received cake: Chocolate Cake 1 Packing received cake: Strawberry Cake 2 Packing received cake: Chocolate Cake 2 Packing received cake: Strawberry Cake 3 Packing received cake: Chocolate Cake 3 Packing received cake:…

Go 中的 Channels
type: item
date: 2013-02-17T22:58:14+00:00
description: Goroutines 允许你以并行的方式运行程序，但是要让这种并行有实际的用途，我们还有一些其它的需求——我们应该能够向运行中的某一个进程传递数据，并且也应该可以从一个运行中的进程中获取数据，这就需要使用到Go的Channels了。一个Channel可以被想象成为一个定义了大小和能力的管道或传送带，我们可以将数据从它的一侧放入，然后在另一侧取出。我们拿蛋糕工厂中的传送带来打比方的话，Channel就是那个传送带，有一台电脑负责制作蛋糕，它将做好的蛋糕放到传送带上面来，然后另一台负责打包的电脑就在等待传送带上面的蛋糕，当它等到一个制作好的蛋糕之后就将其从传送带上面取出然后打包进行下一个处理，在这个过程中，传送带（也就是我们的Channel）起到了让负责制作和打包的两台电脑通讯的功能。传送带就可以被称之为Channel ＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋ ＋ 打好包的蛋 制作电脑 -> -> 蛋糕 -> -> 蛋糕 -> -> 蛋糕 -> 打包电脑 -> -> ＋＋ 糕，可以进 ＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋ ＋ 行其它处理在Go中，关键字 chan 被用来定义Channel，make 关键字则被用来创建它，并且定义它能传送的数据类型（是传送蛋糕的传送带而不是传送鸡蛋的）。ic := make(chan int) sc := make(chan string) mc := make(chan mytype)你可以使用 <- 操作符来向Channel传送或者取回数据，比如：ic := make(chan int) //在某些Goroutines中向其传送数据： ic <- 5 //在另一些Goroutines中取出其中的数据 var recv int recv = <- ic你还可以使用 <- 操作符定义Channel中数据的移动方向：sendOnlyChan := make( <- chan int) //只能发送数据 recvOnlyChan := make( chan -< int) //只能接收数据一个Channel可以容纳数据的容量是十分分重要，它表示了多少个项目可以被同时存储在Channel中，就比如制作蛋糕的电脑一分钟可以制作十个蛋糕，但是打包的电脑却只能一分钟打包五个，这样就会有太多的蛋糕因为无法来得急打包而制作电脑又源源不断的传送带上面放新制作好的蛋糕而被丢掉浪费，这在计算机并行运算中，就是所谓的生产者－消费者同步问题（当然了，Channel是不移动的）。如果一个Channel的容量是 1，那么当一份数据被发送到该Channel之后，就必须要等待这份数据被取出之后，下一份数据才能再被发送进来，发送者与接收者每一次只能在一个时间传送一份数据，而且任何一方都必须要等待另一方执行完成之后才能传送或者接收下一个份数据。我们的示例将从同步Channel开始。我们到现在为止所定义的任何一个Channel都是同步的——新数据必须在旧数据被取走后才能传送进去，现在让我们来看看这个蛋糕制作工厂在Go是如何实现的：package main import ( "fmt" "time" "strconv" ) var i int func makeCakeAndSend(cs chan string) { i = i + 1 cakeName := "Strawberry Cake " + strconv.Itoa(i) fmt.Println("Making a cake and sending...", cakeName) cs <- cakeName // send a strawberry cake } func receiveCakeAndPack(cs chan string) { s := <- cs // get whatever cake is on the channel fmt.Println("Packing received cake: ", s) } func main() { cs := make(chan string) for i := 1; i < 3; i++ { go makeCakeAndSend(cs) go receiveCakeAndPack(cs) } // Sleep for a while so that the program doesn't exit immediately and output is clear for illustration wait, _ := time.ParseDuration("2s") time.Sleep(wait) }输出结果为：Making a cake and sending…

Go 中的 Goroutines
type: item
date: 2013-02-16T22:56:30+00:00
description: Goroutines 允许您能够并行的执行任务——为什么又出来了这么一个新词儿？虽然现在已经有很多形容并行的词了，比如线程、协程、进程等等，但是没有一个已有的词汇能精确地表达出Goroutines的内涵，一个Goroutines就是一个与其它的Goroutine在同一地址空间中并行执行的函数或者方法，一个运行中的程序有一个或者多个Goroutines组成，Go官方文档如下面这么说道：Goroutines被复用到多个系统线程上，所以，如果某一个Goroutine阻塞了，并不会影响其它的Goroutine。 Goroutines为我们隐藏了很多的内部的并行机制，这样的设计让语言的设计者可以根据需要修改它底层并行的实现，以改进其运行性能或者进行硬件优化，而作为语言使用者的我们则不需要修改任何一个代码，就能获得性能的提升。在大多数程序中，我们都是按顺序执行的，因为作为用户的人的处理能力是远远不及计算机的，比如当我们在写代码的时候，我们敲击键盘的速度比计算机处理敲击的速度是慢得多的，所以，计算机在这种情况下一直是处于等待我们的输入的状态。但是对于一台每一秒都处理成千上万请求的服务器来说，这种顺序执行的方法显然是不可能的，如果一百个人同时请求，我们不可能按顺序的执行每一个用户的请求，这个时候就需要并发，在Go中，我们使用Goroutines。Goroutines与普通的函数没有什么不一样的，只是它执行它的时候，需要在函数的名称前面使用一个 go 关键字，如果我们 myFunc() 是一个方法，那么如果要让它以一个Goroutine的方式执行，只需要 go myFunc() 即可——这将使得它以一个新的线程运行，让我们来做一个简单的示例：package main import "fmt" func LoopIt(times int) { for i := 0; i < times; i++ { fmt.Printf("%dt", i) } fmt.Println("--Loop End--") } func main() { LoopIt(10) LoopIt(10) }输出为：0 1 2 3 4 5 6 7 8 9 --Loop End-- 0 1 2 3 4 5 6 7 8 9 --Loop End--然后我们对程序做一个很小的改动：//... func main() { go LoopIt(10) LoopIt(10) }它的输出结果可能就成为：0 0 1 2 1 3 2 4 3 5 4 6 5 7 6 8 7 9 8 --Loop End-- --Loop End--为什么会有这样的区别？因为当我启动一个 Goroutine 之后，LoopIt这个函数立马就会执行，但是它并不会阻塞整个程序，所以，它开始执行之后，它后面的函数也会开始执行，后面的函数就是一个普通的函数，所以它会顺序执行，这使得两人个函数同时运行了，所以我们上面的输出中，这样一来，LoopIt 会一直输出数字，而由于go LoopIt也在执行，它也会输出数据，所以就出现了上面那种情况，这两人个 LoopIt并行了。再来看看下面这个例子：package main import ( "fmt" "time" ) func simulateEvent(name string, timeInSecs string) { fmt.Println("Started ", name, ": Should take", timeInSecs, "seconds") du, _ := time.ParseDuration(timeInSecs) time.Sleep(du) fmt.Println("Finished ", name) } func main() { simulateEvent("First", "10s") simulateEvent("Second", "6s") simulateEvent("Third", "3s") }输出结果为：Started First : Should take 10s seconds Finished First Started Second : Should take 6s seconds Finished Second Started Third : Should take 3s seconds Finished Third这是一个顺序执行的程序，所以，我们可以看到，必须是第一个函数执行完了之后才执行第二个函数，但是我们再来看看下面这个示例：package main import ( "fmt" "time" ) func simulateEvent(name string, timeInSecs string) { fmt.Prin…

Go 的多态
type: item
date: 2013-02-14T22:54:43+00:00
description: 多态，按字面意思来讲就是多种状态，在面向对象编程语言中，接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术，赋值之后，父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作（摘自“Delphi4 编程技术内幕”）。简单的说，就是一句话：允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数（Virtual Function） 实现的。还是用简单的例子来讲明什么要多态吧，我们有一个名为“人”的接口，它有一个方法“我喜欢”，然后一个从火星来的朋友就是程序的使用者，它来到地球之后先问了一个男人：“你喜欢做什么？”，男人调用“我喜欢”这个方法返回给他一个值：“钓鱼”；然后火星人又去问了一个女人同样的问题：“你喜欢做什么？”，然后女人也实现了“人”这个接口，她也调用“我喜欢”这个方法返回给火星人一个值：“购物”……如此这么问下去，发现，每一个人对：“你喜欢做什么？”这个问题的问题都是不一样的，而这种不一样，就是*多态*——从同一种类型的同一个方法中获取完全不同的结果——在这个例子中，接接口就是“人”。在Go中，我们使用接口来实现多态，如果你看这篇文章时，还不了解Go的接口，建议你先看看我的另一篇文章Go 中的接口，然后再返回来看本文。创建我们创建了一个接口，并且有其它的类型实现了这个接口，那么我们就可以通过这个接口直接访问到每一个类型中所定义的那个方法，而不需要再知道它具体是什么类型，让我们用Go来实现上面那个关于火星人问问题的例子：package main import "fmt" // 定义人这个接口 type Human interface { // 它有一个方法 iLove iLove() string } // 定义男人 type Man struct {} // 实现人这个接口 func (man Man) iLove() string { return "钓鱼" } //重复上面的定义 type Woman struct {} func (woman Woman) iLove() string { return "购物" } func main() { m := new (Man) w := new (Woman) // 一个保存人的数组，它有两人个元素 // 一个为 Man 类型，一个为Woman类型 humans := [...]Human{m,w} for i, _ := range (humans) { // 直接调用接口的 iLove 方法 fmt.Println("我喜欢", humans[i].iLove()) } }输出为：我喜欢 钓鱼 我喜欢 购物在上面的例子了，我们都是调用了 Human 的 iLove 方法，但是得到了两人个结果，而且我们是直接使用 for 循环来对保存了 Human 数据的数组进行遍历而不是明显地使用 Man.iLove() 方法或者Woman.iLove() 方法。现在又有一个新问题产生了，这火星人并不知道在地球上我们把人和其它生物是作不同对待的，所以，他还跑去问了一只狗：“你喜欢做什么啊”？狗很客气的“汪！汪汪！汪汪汪！”，那这种新情况出现了我们该怎么办？好办，让我们再来扩展一下上面的例子：package main import "fmt" // 定义生物这个接口 type Organism interface { // 它有一个方法 iLove iLove() string } // 定义人这种生物 type Human struct {} // 定义人对生物这个接口的实现 func (human Human) iLove() string { return "做人该做的事情，我是一只人，不是一个狗！" } // 定义男人 type Man struct { Human } func (man Man) iLove() string { return "钓鱼，我是一只男人。" } //重复上面的定义 type Woman struct { Human } func (woman Woman) iLove() string { return "购物，我是一只女人。" } // 定义狗 type Dog struct {} func (dog Dog) iLove() string { return "汪！汪汪！汪汪汪！我是一个狗！不是一只人。" } func main() { h := new (Human) m := new (Man) w := new (Woman) d := new (Dog) organisms := [.…

Go 的接口 —— 进化与高可用性的设计
type: item
date: 2013-02-10T22:53:27+00:00
description: 在我那篇《Go 中的接口》的文章中，我用了一个很简单的示例来讲明Go的接口实现——矩形与方形同时实现了形状接口。但是我们要清楚的认识到，Go的接口并不是一种C或者Java的变体，而是具有了很多的特性，它是大规模程序开发及高可用性程序开发所需要的关键。先看看Java里面接口的实现方法，然后再返回来看看Go的接口的实现方式，我们就能很容易发现为什么说Go的接口实现可用性可扩展性更高，而且进化到另一个层次。我们的数据模型是 *Bus*，它同时实现了两人个接口——只有可用空间的立方体以及可以运输乘客的运输工具：// 第一步：在编码之前，先设计好接口以及类的继承 // 第二步：将现实世界中的对象抽象成为我们可以在程序中使用的接口 interface PublicTransport { int PassengerCapacity(); } interface Cuboid { int CubicVolumn(); } // 第三步：创建数据结构同时去实现我们在第二步中定义的所有接口 public class Bus implements PublicTransport, Cuboid { // 定义类的数据结构 int l, b, h, rows, seatsPerRow; public Bus(int l, int b, int h, int rows, int seatsPerRow) { this.l =l; this.b = b; this.h = h; this.rows = rows; this.seatsPerRow = seatsPerRow; } // 定义方法的实现 public int CubicVolumn() { return l*b*h; } public int PassengerCapacity() { return rows * seatsPerRow; } // 在 main 函数中调用类及其方法 public static void main() { Bus b = new Bus(10, 6, 3, 10, 5); System.out.Println(b.CubicVolume()); System.out.Println(b.PassengerCapacity()); } }在上面的Java示例中，你必须在实现接口方法之前明确地定义类的数据层次结构，并且需要使用implements 这个关键字，C# 中则是将接口名称在 : 符号后面列出来代替 implements 关键字，在这种情况下，任何这部分的需求的变更都会影响到项目的核心模块。绝大数的项目就是在这种先定义接口然后去实现最后发现不能完全需求，又返回去修改接口中再实现再修改，如此往复，直到接口已经考虑到各方面的因素／涵盖了所有需求——而如果此时项目的需求还在变化的话，那工作量就更大了。在完全确定了接口之后，就不再推荐修改这些最底层的接口了，即使是项目管理员也不允许随意的修改它，而确实需要修改的话，有可能还需要通过各种各样大大小小的项目组研讨会等最后才能确定是否修改以及如何修改，因为接口做为最底层，它的一点修改，都会影响到上层的实现，如果一个接口有一百个实现，那么它的修改将需要对其一百个实现也做相应的修改。或者还有另一种方法，那就是重新创建一个新的接口，接口的定义就是现在这个接口修改后的定义，然后再创建新的类去实现新的接口。当我们的项目达到某一个层次之后，发现所有的这些事情已经完全失控了。那有没有更好的办法？我们先来看看上面这个Java示例的的Go的实现：package main import "fmt" // Go第一步：定义你的数据结构 type Bus struct { l, b, h int rows, seatsPerRow int } // Go第二步：定义一个现实世界对象的抽象层 type Cuboider interface { CubicVolume() int } // Go第三步：实现接口的方法 func (bus Bus) CubicVolume() int { return bus.l * bus.b * bus.h } // 重复上面的二三步 type PublicTransporter interface { PassengerCapacity() int } func (bus Bus) PassengerCapacity() int { return bus.rows * bus.seatsPerRow } func main() { bus := Bus{ l:10, b:6, h:3, rows: 10, seatsPerRow: 5} fmt.Println("Cubic volume of bus: "…