13.3. controller 设计

未匹配的标注

传统的 MVC 框架大多数是基于 Action 设计的后缀式映射,然而,现在 Web 流行 REST 风格的架构。尽管使用 Filter 或者 rewrite 能够通过 URL 重写实现 REST 风格的 URL,但是为什么不直接设计一个全新的 REST 风格的 MVC 框架呢?本小节就是基于这种思路来讲述如何从头设计一个基于 REST 风格的 MVC 框架中的 controller,最大限度地简化 Web 应用的开发,甚至编写一行代码就可以实现 “Hello, world”。

controller 作用

MVC 设计模式是目前 Web 应用开发中最常见的架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的用户界面 (UI)。Model 指后台返回的数据;View 指需要渲染的页面,通常是模板页面,渲染后的内容通常是HTML;Controller 指 Web 开发人员编写的处理不同 URL 的控制器,如前面小节讲述的路由就是 URL 请求转发到控制器的过程,controller 在整个的 MVC 框架中起到了一个核心的作用,负责处理业务逻辑,因此控制器是整个框架中必不可少的一部分,Model 和 View 对于有些业务需求是可以不写的,例如没有数据处理的逻辑处理,没有页面输出的 302 调整之类的就不需要 Model 和 View,但是 controller 这一环节是必不可少的。

beego 的 REST 设计

前面小节介绍了路由实现了注册 struct 的功能,而 struct 中实现了 REST 方式,因此我们需要设计一个用于逻辑处理 controller 的基类,这里主要设计了两个类型,一个 struct、一个 interface


type Controller struct {
    Ct        *Context
    Tpl       *template.Template
    Data      map[interface{}]interface{}
    ChildName string
    TplNames  string
    Layout    []string
    TplExt    string
}

type ControllerInterface interface {
    Init(ct *Context, cn string)    // 初始化上下文和子类名称
    Prepare()                       // 开始执行之前的一些处理
    Get()                           // method=GET 的处理
    Post()                          // method=POST 的处理
    Delete()                        // method=DELETE 的处理
    Put()                           // method=PUT 的处理
    Head()                          // method=HEAD 的处理
    Patch()                         // method=PATCH 的处理
    Options()                       // method=OPTIONS 的处理
    Finish()                        // 执行完成之后的处理        
    Render() error                  // 执行完 method 对应的方法之后渲染页面
}

那么前面介绍的路由 add 函数的时候是定义了 ControllerInterface 类型,因此,只要我们实现这个接口就可以,所以我们的基类 Controller 实现如下的方法:


func (c *Controller) Init(ct *Context, cn string) {
    c.Data = make(map[interface{}]interface{})
    c.Layout = make([]string, 0)
    c.TplNames = ""
    c.ChildName = cn
    c.Ct = ct
    c.TplExt = "tpl"
}

func (c *Controller) Prepare() {

}

func (c *Controller) Finish() {

}

func (c *Controller) Get() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Post() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Delete() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Put() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Head() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Patch() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Options() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Render() error {
    if len(c.Layout) > 0 {
        var filenames []string
        for _, file := range c.Layout {
            filenames = append(filenames, path.Join(ViewsPath, file))
        }
        t, err := template.ParseFiles(filenames...)
        if err != nil {
            Trace("template ParseFiles err:", err)
        }
        err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data)
        if err != nil {
            Trace("template Execute err:", err)
        }
    } else {
        if c.TplNames == "" {
            c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
        }
        t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames))
        if err != nil {
            Trace("template ParseFiles err:", err)
        }
        err = t.Execute(c.Ct.ResponseWriter, c.Data)
        if err != nil {
            Trace("template Execute err:", err)
        }
    }
    return nil
}

func (c *Controller) Redirect(url string, code int) {
    c.Ct.Redirect(code, url)
}

上面的 controller 基类已经实现了接口定义的函数,通过路由根据 url 执行相应的 controller 的原则,会依次执行如下:


Init()      初始化
Prepare()   执行之前的初始化,每个继承的子类可以来实现该函数
method()    根据不同的 method 执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403
Render()    可选,根据全局变量 AutoRender 来判断是否执行
Finish()    执行完之后执行的操作,每个继承的子类可以来实现该函数

应用指南

上面 beego 框架中完成了 controller 基类的设计,那么我们在我们的应用中可以这样来设计我们的方法:


package controllers

import (
    "github.com/astaxie/beego"
)

type MainController struct {
    beego.Controller
}

func (this *MainController) Get() {
    this.Data["Username"] = "astaxie"
    this.Data["Email"] = "astaxie@gmail.com"
    this.TplNames = "index.tpl"
}

上面的方式我们实现了子类 MainController,实现了 Get 方法,那么如果用户通过其他的方式 (POST/HEAD等) 来访问该资源都将返回405,而如果是 Get 来访问,因为我们设置了 AutoRender=true,那么在执行完 Get 方法之后会自动执行 Render 函数,就会显示如下界面:

index.tpl 的代码如下所示,我们可以看到数据的设置和显示都是相当的简单方便:


<!DOCTYPE html>
<html>
  <head>
    <title>beego welcome template</title>
  </head>
  <body>
    <h1>Hello, world!{{.Username}},{{.Email}}</h1>
  </body>
</html>

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
Summer
讨论数量: 0
发起讨论 只看当前版本


暂无话题~