1. OOP
OOP 的思想, 无疑是非常实用有效的. 事实是, 无论语言是否直接支持面向对象的编程. 程序员在写代码的时候常常会应用 OOP 的思想.
但是 Go 语言下没有类(Class), 没有 this
指针, 没有多态, 只有复合.
应用 OOP 的思想, WEB 应用下控制器常见形式祖先类型的写法(示意代码).
type Controller struct {
Data interface{} // WEB 应用通常都有输出数据
Req *http.Request // 来自 http 的请求对象
Res http.ResponseWriter // 响应对象
}
// 官方 net/http 包要求的接口方法
func (p *Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.Req = r
p.Res = w
if r.Method == "POST" {
p.Post()
p.Out()
}
}
// 对应 http POST 方式
func (p *Controller) Post() {
// WEB开发常见的控制器方法, 用于处理客户 http Post 方式的请求
// 继承者应该覆盖这个方法, 否则认为不允许这样访问, 那就返回 403 拒绝访问
p.Res.WriteHeader(403)
}
// 可以预计的共性的处理, 比如向 Res 输出 Data, 也许这是一个模板处理的过程
// 没有共性的处理, 复合仅剩下封装属性字段的作用.
func (p *Controller) Out() {
if p.Data == nil {
panic("继承者竟然没有任何输出数据, 你让我怎么办?")
}
// 假设 Data 非常简单,就是一个string
p.Res.Write([]byte(p.Data.(string)))
}
// Login 控制器
type Login struct {
Controller // 复合
}
// 这里必须覆盖 Controller.Post, 以表示 Login 的具体行为
func (p *Login) Post() {
if p.Req.Form.Get("login_name") == "" {
p.Data = "无效的登录名"
return
}
// 这里省略登录成功的过程
p.Data = "登录成功"
}
// 需要一个接口定义, 这里只有简化的 post 请求.
type HttpPostController interface {
ServeHTTP(http.ResponseWriter, *http.Request)
Post()
Out()
}
虽然 Go 只有复合, 但这并不妨碍实现类似 OOP 继承的方式.
Login
这样用
http.Handle("/login", &Login{})
但是很明显,现实中这样的用法是错误的, 因为 WEB 的请求是并发的, 这样写所有并发的请求都由同一个&Login{}
去处理.
Req
,Res
,Data
会在并发中被重复赋值.
我们需要对每一个请求都及时生成一个 Login
对象的机制.
重新审视 http.Handle
的第二参数http.Handler
接口. (官方包 server.go 中的代码)
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
这个http.Handler
接口其实只是被当作一个函数使用了. 并发问题留给使用者自己解决.
对于这种在属性中有数据设置的复合结构. 需要在并发下及时生成新对象.
HandlerFunc
的工作方式就是我们要的, 可以这样做
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
p := &Login{}
p.ServeHTTP(w, r)
})
每次请求都有新的Login
对象产生. 当然这个写法很生硬, 如果有100个控制器,难道还要写100个不同的写法! 多数 WEB 框架会通过反射包的支持, 构建新对象. 的确是个好方法. 这种方法, 这里不打算费笔墨介绍.
2. 函数
这是一种看上去蛋疼的用法.
func main() {
http.HandleFunc("/login", login)
}
func login(w http.ResponseWriter, r *http.Request) {
var data interface{}
var post = func() {
if r.Form.Get("login_name") == "" {
data = "无效的登录名"
return
}
// 省略具体过程
data = "登录成功"
}
var out = func() {
if data == nil {
w.WriteHeader(403)
return
}
w.Write([]byte(p.Data.(string)))
}
post()
out()
}
完全就是个函数, 但是并发下, 这完全没有问题. 函数内的局部变量都是动态创建的. 这是 Go 要干的事情. 当然你甚至可以直接用闭包的方法. 连函数命名都省掉.
3. 构造函数
Go 没有构造函数的概念的. 没关系我们模拟一个. 一般构造用Constructor
, 这里选用 New
更符合 Go 代码风格.
// 给控制器接口增加一个构造函数
type HttpPostController interface {
New() HttpPostController
ServeHTTP(w http.ResponseWriter, r *http.Request)
Post()
Out()
}
// 扩充 Login , 实现 HttpPostController 接口
func (p *Login) New() HttpPostController {
return &Login{}
}
// 需要一个能配合 http.Handler 支持构造函数
type HandlerNew struct {
Constructor HttpPostController
}
// http.Handler 接口实现
func (p HandlerNew) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := p.Constructor.New()
c.ServeHTTP(w, r)
}
这样使用
http.Handle("/login", HandlerNew{new(Login)})
其实就是一层层的 http.Handler
接口包裹起来.
4. 谁知道呢
闭包和构造函数的方法, 什么时候用? 我只是把他们罗列出来, 我不知道什么样的场景可以用到. 或许后续的实现中就会用到, 或许用的时候改变一些代码和参数就变的很好使, 谁知道呢!