Go语言的接口比较特殊,因为它是满足隐式实现的。也就是说,我们无需给具体类型定义所有满足足的接口类型,只需要让类型拥有一些简单必要的方法。这样我们新建一个接口类型,满足具体类型,并且我们不需要更改这些类型的定义。当我们使用的类型来自于不受我们控制的包时,这种机制比较有用
在下面的程序中,我们创建一个ServeMux并且使用它将URL和相应处理/list和Price操作的handle联系起来,这些操作的逻辑都已经分配到不同的方法中了,然后我们再调用ListenAndServe函数中使用ServeMux为主要的handler
func main() {
db := database{'shoes': 50, 'socks': 5}
mux := http.NewServeMux()
mux.Handle('/list', http.HandlerFunc(db.list))
mux.Handle('/price', http.HandlerFunc(db.price))
log.Fatal(http.ListenAndServe('localhost:8000', mux))
}
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, '%s: %s
', item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get('item')
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, 'no such item: %q
', item)
return
}
fmt.Fprintf(w, '%s
', price)
}
现在运行程序,当我们访问浏览器时,便可以看到如下信息
第一章,我们粗略了解了用net/http包实现网络客户端和服务器,本节我们将会对那些基于http.Hander接口的服务器API做更进一步的学习
package http
type Handler interface {
ServeHTTP(w ResponseWtiter,r *Request)
}
func ListenAndServe(address string, h Handler) error
实际上,这个技巧让一个单一的类型例如database以多种方式满足http.Handler接口:一种通过它的list方法,一种通过它的price方法等
ListenAndServe函数需要一个l例如“localhost:8000”的地址和一个Handler接口实例
运行并且访问如下地址
http://localhost:8000/price?item=socks
$5.00
http://localhost:8000/price?item=hat
no such itme:'hat'
注意:web服务器在一个新的协程种调用每一个handler,所以当handler获取其它协程或者这个handler本身的其它请求可以访问到变量时,一定要使用预防措施,比如锁机制。后面我们将会讲到并发相关的知识
显然,我们可以通过增加case的方式来实现不同情况下的分流,但是在实际中,我们一般把不同的case写成不同的函数或者方法
现在我们来实现这一点:我们使用了一个switch结构
func main() {
db := database{'shoes':50,'socks':5}
log.Fatal(http.ListenAndServe('localhost:8000',db))
}
type dollars float32
func(d dollars) String() string {return fmt.Sprintf('$%.2f',d)}
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter,req *http.Request) {
switch req.URL.Path {
case '/list':
for item,price :=range db {
fmt.Fprintf(w,'%s:%s
',item,price)
}
case '/price':
item:= req.URL.Query().Get('item')
price,ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w,'no such itme:%q
',item)
return
}
fmt.Fprintf(w,'%s
',price)
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w,'no such page:%s
',req.URL)
}
}
所以为了方便,net/http包提供了一个全局的ServeMux实例DefaultAndServeMux和包级别的http.handle和http.HandlerFunc函数。现在为了使用DefaultServeMux作为服务器的主handler,我们不需要将它传递给ListenAndServe函数;nil值就可以工作
但是在大多数程序中,一个web服务器就足够了,在一个应用程序的多个文件中定HTTPhandler也非常典型,如果它们必须全部都显式的注册到这个应用的ServeMux实例上会比较麻烦
路径相似的url处理的逻辑也是相似的,net/http包提供了一个请求多路器ServerMux来简化和handlers的联系
接口类型是对其它类型行为的抽象和概括.接口类型不会和特定的实现细节绑定在一起,这种抽象的方式能让我们的函数更加的灵活和更具有适应能力
一个ServeMux将一批http.Handlers聚集到一个单一的http.Handler中,这也再一次表明满足同一个接口的不同类型是可以替换的:web服务器将请求指派给任意的http.Handler而不需要考虑它后面的具体类型
从上面的代码中很容易看出应该怎么构建一个程序:两个不同的web服务器监听不同的端口,并且定义不同的URL将它们指派到不同handler,我们只需要再构建一个ServeMux并且再调用一次ListenAndServe
虽然我们现在可以访问这个地址并且能够成功获得信息,但是我们的信息不够细致,在实践中,我们更多的业务逻辑是访问特定的路径获得特定的信息
因为handler通过这种方式注册非常普遍,ServeMux有一个非常方便的HandlerFunc方法,它帮助我们简化handler注册代码成这样
对于复杂的应用,一些ServeMux可以通过组合来处理更加复杂的路由请求,Go语言没有目前没有一个权威的Web框架,就像Ruby语言有Rails和python有Django,这并不是Go语言的劣势,而是Go语言太强用不上
语句http.HandlerFunc(dlist)是一个转换而非函数调用,因为http.HandlerFunc是一个类型,定义如下:
package http
type HandlerFunc func(w ResponseWriter,r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter,r *Request) {
f(w,r)
}
也就是说dlist的调用会援引一个接收者是db的databaslist方法.所以dlist是一个实现了handler类似行为的函数,但是它因为没有方法,所以它不满足http.Handler接口,并且不能直接传递给mux.Handle
我们现在来构建一个电子商务网站,当我们访问特定服务器地址时,我们就可以看到产品和其价格信息,在此之前我们先得把这些基本的信息在底层构建好
type dollars float32
func(d dollars) String() string {return fmt.Sprintf('$%.2f',d)}
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter,req *http.Request) {
for item, price := range db {
fmt.Fprintf(w,'%s: %s
',item,price)
}
}
func main() {
db := database{'shoes':50,'socks':5}
log.Fatal(http.ListenAndServe('localhost:8000',db))
}
HanflerFunc显示了在Go语言中接口机制中比较特殊的一点,这是一个实现了http.Handler的函数类型。ServeHTTP方法的行为是调用了它函数的本身,因此HandlerFunc是一个让函数满足一个接口的适配器,这里函数和这个接口仅有的方法有着相同的签名
我们看看两个注册到handlers的调用,第一个db,list是一个方法值,它是下面这个类型的值
文章为作者独立观点,不代表股票交易接口观点