<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                面向對象 ======== [OOP][1] 的思想, 無疑是非常實用有效的. 事實是, 無論語言是否直接支持面向對象的編程. 程序員在寫代碼的時候常常會應用 OOP 的思想. Go 語言下沒有類(Class), 沒有構造函數, 沒有 `this` 指針, 沒有多態, 只有復合對象(或匿名屬性). 復合對象和繼承是完全不同的. 在以后的文字中, 繼承這個詞不再代表一般 OOP 下的繼承, 指的是復合對象. 應用 OOP 的思想, WEB 應用下控制器常見形式祖先類型的示意寫法(現實中沒有太大意義). ```go // 定義基礎控制器結構 type BaseController struct { Data interface{} // 應用要維護的數據 Req *http.Request // 請求對象 Res http.ResponseWriter // 響應對象 } // 官方 net/http 包要求實現的接口 func (p *BaseController) ServeHTTP(w http.ResponseWriter, r *http.Request) { p.Req = r // 保存起來供實例使用 p.Res = w if r.Method == "POST" { p.Post() } } // 對應 http POST 方式 func (p *BaseController) Post() { // 繼承者必須覆蓋這個方法, 否則認為不允許 POST 訪問 // BaseController 是不可能知道繼承者要做什么, 那就只能返回 403 拒絕訪問 p.Res.WriteHeader(403) } // Login 控制器 type Login struct { BaseController // 匿名復合 } // 這里必須覆蓋 BaseController.Post, 以實現 Login 的具體行為 func (p *Login) Post() { if p.Req.Form.Get("login_name") == "" { p.Data = "無效的登錄名" return } // 這里省略登錄成功的過程 p.Data = "登錄成功" } // 把這些行為定義成接口 type Controller interface { ServeHTTP(http.ResponseWriter, *http.Request) Post() } ``` 用例 ```go http.Handle("/login", &Login{}) ``` **但是這有并發問題** 并發下維護上下文 ================ 很明顯現實中這樣的用法是錯誤的, 因為 WEB 的請求是**并發**的, 這樣寫所有并發的請求都由同一個 `&Login{}` 去處理響應, `Req`,`Res`,`Data` 在并發中都被指向相同的對象. 這是無法正常工作的. 這就是常說的維護上下文, Context. **并發環境每一個請求都要有維護獨占數據的能力.** 除非沒有獨占數據要維護. 先重新審視 官方包 server.go 中的代碼 ```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`接口其實只是被當作一個函數使用了. 并發問題留給使用者自己解決. 可以這樣做 ```go http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { p := &Login{} p.ServeHTTP(w, r) }) ``` 每次請求都有新的`Login`對象產生. 當然這個寫法很生硬, 如果有 100 個控制器, 難道還要寫 100 個不同的寫法?! 可以采用下面的方法. ## 函數法 不用結構體直接使用函數, 所有上下文維護都在函數內部定義成局部變量, 局部變量在函數內部是獨占的. ```go 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 = "登錄成功" } post() } ``` 完全就是個函數, 但是并發下, 這完全沒有問題. 問題在于如何和其他的模塊進行數據溝通, 方法確實有, 比如可以用 [gorilla/context][2]. 但是無法想象整個項目都用這種寫法. ## 約定構造函數 Go 沒有構造函數的概念的. 沒關系我們約定一個. 其他語言常用`Constructor`, 這里選用 `New` 更符合 Go 風格. ```go // 給控制器接口增加一個構造函數 type Controller interface { New() Controller ServeHTTP(w http.ResponseWriter, r *http.Request) Post() } // 擴充 Login , 實現 New 方法 func (p *Login) New() Controller { return &Login{} } // 定義一個 http.Handler 接口, 支持構造函數 type HandlerNew struct { Controller Controller } // http.Handler 接口實現 func (p *HandlerNew) ServeHTTP(w http.ResponseWriter, r *http.Request) { c := p.Constructor.New() c.ServeHTTP(w, r) } ``` 用例 ```go http.Handle("/login", HandlerNew{new(Login)}) ``` ## 用反射Value.New 反射包 `reflect` 中的 `reflect.Value` 有 `New` 方法, 可以動態的構造出一個新對象. 有些框架就是采用了這種方法, 但是用 `Value.New` 只是得到一個空屬性對象, 要對對象進行初始化依然要約定初始化函數, 這反而比 約定構造函數費事兒. 這里就不具體討論了. Martini下的并發 =============== 筆者在發現 Martini 之前也很困惑到底用什么辦法更好, 所以寫了早期的 [TypePress][3], 和對應的 [Go-Blog-In-Action][4]. Martini 巧妙解決了 WEB 并發中的上下文維護. Martini 發現了 WEB 開發中單個請求響應要維護的上下文有這樣的事實: - 數據類型是預知的 很顯然 - 數據類型有限的 很顯然 - 數據類型常常是唯一的 就算偶有不唯一, 定義個別名就行了, 這很容易 - 階段響應, 完整的響應過程往往分多個階段, 為了代碼復用, 各個階段有獨立的代碼 因此 Martini 采用了這樣的方案: 1. Martini 負責動態構建一個 Context 對象, Context 繼承自 Injector 2. Martini 的 Handler 是一組 []Hanlder, 有序執行 3. 使用者對 Handler 進行階段性功能劃分, 先執行的負責準備好上下文數據 dat 4. 通過 Map(dat) 保存到 Context. (實際由 Injector 負責) 5. 后續 Hander 要用 dat, 直接在 Handler 函數中加入參數 dat datType 6. Injector 通過 reflect 分析 Handler 的參數類型, 并取出 dat, 調用 Handler 這個方法比 gorilla/context 更高效實用, 雖然都是用 map 保存上下文數據, 差別有 - gorilla/context 的 map 是全局的, Martini 保存到 Context - gorilla/context 只是做了 key/value 存儲, Martini 完成了 Handler 調用 這和 OOP 有何關系? 關系是 golang 不是真正的繼承, 這給維護上下文數據造成了問題. Martini 解決了上下文數據維護問題, 應用可以放心的用復合寫邏輯代碼. 上下文數據交給 Martini 就好. [1]: http://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1 [2]: https://github.com/gorilla/context [3]: https://github.com/achun/typepress [4]: https://github.com/achun/Go-Blog-In-Action/tree/master
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看