OOP

OOP 的思想, 无疑是非常实用有效的. 事实是, 无论语言是否直接支持面向对象的编程. 程序员在写代码的时候常常会应用 OOP 的思想.

但是 Go 语言下没有类(Class), 没有 this 指针, 没有多态, 只有复合.

应用 OOP 的思想, WEB 应用下控制器常见形式祖先类型的写法(示意代码).

  1. type Controller struct {
  2. Data interface{} // WEB 应用通常都有输出数据
  3. Req *http.Request // 来自 http 的请求对象
  4. Res http.ResponseWriter // 响应对象
  5. }
  6. // 官方 net/http 包要求的接口方法
  7. func (p *Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  8. p.Req = r
  9. p.Res = w
  10. if r.Method == "POST" {
  11. p.Post()
  12. p.Out()
  13. }
  14. }
  15. // 对应 http POST 方式
  16. func (p *Controller) Post() {
  17. // WEB开发常见的控制器方法, 用于处理客户 http Post 方式的请求
  18. // 继承者应该覆盖这个方法, 否则认为不允许这样访问, 那就返回 403 拒绝访问
  19. p.Res.WriteHeader(403)
  20. }
  21. // 可以预计的共性的处理, 比如向 Res 输出 Data, 也许这是一个模板处理的过程
  22. // 没有共性的处理, 复合仅剩下封装属性字段的作用.
  23. func (p *Controller) Out() {
  24. if p.Data == nil {
  25. panic("继承者竟然没有任何输出数据, 你让我怎么办?")
  26. }
  27. // 假设 Data 非常简单,就是一个string
  28. p.Res.Write([]byte(p.Data.(string)))
  29. }
  30. // Login 控制器
  31. type Login struct {
  32. Controller // 复合
  33. }
  34. // 这里必须覆盖 Controller.Post, 以表示 Login 的具体行为
  35. func (p *Login) Post() {
  36. if p.Req.Form.Get("login_name") == "" {
  37. p.Data = "无效的登录名"
  38. return
  39. }
  40. // 这里省略登录成功的过程
  41. p.Data = "登录成功"
  42. }
  43. // 需要一个接口定义, 这里只有简化的 post 请求.
  44. type HttpPostController interface {
  45. ServeHTTP(http.ResponseWriter, *http.Request)
  46. Post()
  47. Out()
  48. }

虽然 Go 只有复合, 但这并不妨碍实现类似 OOP 继承的方式.
Login 这样用

  1. http.Handle("/login", &Login{})

但是很明显,现实中这样的用法是错误的, 因为 WEB 的请求是并发的, 这样写所有并发的请求都由同一个&Login{}去处理.
Req,Res,Data 会在并发中被重复赋值.
我们需要对每一个请求都及时生成一个 Login 对象的机制.

重新审视 http.Handle 的第二参数http.Handler接口. (官方包 server.go 中的代码)

  1. type Handler interface {
  2. ServeHTTP(ResponseWriter, *Request)
  3. }
  4. func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
  5. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  6. DefaultServeMux.HandleFunc(pattern, handler)
  7. }
  8. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  9. mux.Handle(pattern, HandlerFunc(handler))
  10. }
  11. type HandlerFunc func(ResponseWriter, *Request)
  12. // ServeHTTP calls f(w, r).
  13. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  14. f(w, r)
  15. }

这个http.Handler接口其实只是被当作一个函数使用了. 并发问题留给使用者自己解决.
对于这种在属性中有数据设置的复合结构. 需要在并发下及时生成新对象.

HandlerFunc的工作方式就是我们要的, 可以这样做

  1. http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
  2. p := &Login{}
  3. p.ServeHTTP(w, r)
  4. })

每次请求都有新的Login对象产生. 当然这个写法很生硬, 如果有100个控制器,难道还要写100个不同的写法! 多数 WEB 框架会通过反射包的支持, 构建新对象. 的确是个好方法. 这种方法, 这里不打算费笔墨介绍.

函数

这是一种看上去蛋疼的用法.

  1. func main() {
  2. http.HandleFunc("/login", login)
  3. }
  4. func login(w http.ResponseWriter, r *http.Request) {
  5. var data interface{}
  6. var post = func() {
  7. if r.Form.Get("login_name") == "" {
  8. data = "无效的登录名"
  9. return
  10. }
  11. // 省略具体过程
  12. data = "登录成功"
  13. }
  14. var out = func() {
  15. if data == nil {
  16. w.WriteHeader(403)
  17. return
  18. }
  19. w.Write([]byte(p.Data.(string)))
  20. }
  21. post()
  22. out()
  23. }

完全就是个函数, 但是并发下, 这完全没有问题. 函数内的局部变量都是动态创建的. 这是 Go 要干的事情. 当然你甚至可以直接用闭包的方法. 连函数命名都省掉.

构造函数

Go 没有构造函数的概念的. 没关系我们模拟一个. 一般构造用Constructor, 这里选用 New 更符合 Go 代码风格.

  1. // 给控制器接口增加一个构造函数
  2. type HttpPostController interface {
  3. New() HttpPostController
  4. ServeHTTP(w http.ResponseWriter, r *http.Request)
  5. Post()
  6. Out()
  7. }
  8. // 扩充 Login , 实现 HttpPostController 接口
  9. func (p *Login) New() HttpPostController {
  10. return &Login{}
  11. }
  12. // 需要一个能配合 http.Handler 支持构造函数
  13. type HandlerNew struct {
  14. Constructor HttpPostController
  15. }
  16. // http.Handler 接口实现
  17. func (p HandlerNew) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  18. c := p.Constructor.New()
  19. c.ServeHTTP(w, r)
  20. }

这样使用

  1. http.Handle("/login", HandlerNew{new(Login)})

其实就是一层层的 http.Handler 接口包裹起来.

谁知道呢

闭包和构造函数的方法, 什么时候用? 我只是把他们罗列出来, 我不知道什么样的场景可以用到. 或许后续的实现中就会用到, 或许用的时候改变一些代码和参数就变的很好使, 谁知道呢!