在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 2.5 面向?qū)ο?/span>
7 文本處理
3 Web基礎(chǔ)
14 擴展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫進(jìn)行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計一個Web框架
14.3 表單及驗證支持
12 部署與維護(hù)
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗證表單的輸入
10.1 設(shè)置默認(rèn)地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設(shè)計
7.6 字符串處理
13.2 自定義路由器設(shè)計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計
2.6 interface
14.4 用戶認(rèn)證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結(jié)
3.2 Go搭建一個Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結(jié)
8.4 RPC

2.5 面向?qū)ο?/h1>

前面兩章我們介紹了函數(shù)和struct,那你是否想過函數(shù)當(dāng)作struct的字段一樣來處理呢?今天我們就講解一下函數(shù)的另一種形態(tài),帶有接收者的函數(shù),我們稱為method

method

現(xiàn)在假設(shè)有這么一個場景,你定義了一個struct叫做長方形,你現(xiàn)在想要計算他的面積,那么按照我們一般的思路應(yīng)該會用下面的方式來實現(xiàn)

package main
import "fmt"

type Rectangle struct {
    width, height float64
}

func area(r Rectangle) float64 {
    return r.width*r.height
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    fmt.Println("Area of r1 is: ", area(r1))
    fmt.Println("Area of r2 is: ", area(r2))
}

這段代碼可以計算出來長方形的面積,但是area()不是作為Rectangle的方法實現(xiàn)的(類似面向?qū)ο罄锩娴姆椒ǎ?,而是將Rectangle的對象(如r1,r2)作為參數(shù)傳入函數(shù)計算面積的。

這樣實現(xiàn)當(dāng)然沒有問題咯,但是當(dāng)需要增加圓形、正方形、五邊形甚至其它多邊形的時候,你想計算他們的面積的時候怎么辦???那就只能增加新的函數(shù)咯,但是函數(shù)名你就必須要跟著換了,變成area_rectangle, area_circle, area_triangle...

像下圖所表示的那樣, 橢圓代表函數(shù), 而這些函數(shù)并不從屬于struct(或者以面向?qū)ο蟮男g(shù)語來說,并不屬于class),他們是單獨存在于struct外圍,而非在概念上屬于某個struct的。

http://wiki.jikexueyuan.com/project/go-web-programming/images/2.5.rect_func_without_receiver.png" alt="" />

圖2.8 方法和struct的關(guān)系圖

很顯然,這樣的實現(xiàn)并不優(yōu)雅,并且從概念上來說"面積"是"形狀"的一個屬性,它是屬于這個特定的形狀的,就像長方形的長和寬一樣。

基于上面的原因所以就有了method的概念,method是附屬在一個給定的類型上的,他的語法和函數(shù)的聲明語法幾乎一樣,只是在func后面增加了一個receiver(也就是method所依從的主體)。

用上面提到的形狀的例子來說,method area() 是依賴于某個形狀(比如說Rectangle)來發(fā)生作用的。Rectangle.area()的發(fā)出者是Rectangle, area()是屬于Rectangle的方法,而非一個外圍函數(shù)。

更具體地說,Rectangle存在字段length 和 width, 同時存在方法area(), 這些字段和方法都屬于Rectangle。

用Rob Pike的話來說就是:

"A method is a function with an implicit first argument, called a receiver."

method的語法如下:

func (r ReceiverType) funcName(parameters) (results)

下面我們用最開始的例子用method來實現(xiàn):

package main
import (
    "fmt"
    "math"
)

type Rectangle struct {
    width, height float64
}

type Circle struct {
    radius float64
}

func (r Rectangle) area() float64 {
    return r.width*r.height
}

func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}

    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

在使用method的時候重要注意幾點

  • 雖然method的名字一模一樣,但是如果接收者不一樣,那么method就不一樣
  • method里面可以訪問接收者的字段
  • 調(diào)用method通過.訪問,就像struct里面訪問字段一樣

圖示如下:

http://wiki.jikexueyuan.com/project/go-web-programming/images/2.5.shapes_func_with_receiver_cp.png" alt="" />

圖2.9 不同struct的method不同

在上例,method area() 分別屬于Rectangle和Circle, 于是他們的 Receiver 就變成了Rectangle 和 Circle, 或者說,這個area()方法 是由 Rectangle/Circle 發(fā)出的。

值得說明的一點是,圖示中method用虛線標(biāo)出,意思是此處方法的Receiver是以值傳遞,而非引用傳遞,是的,Receiver還可以是指針, 兩者的差別在于, 指針作為Receiver會對實例對象的內(nèi)容發(fā)生操作,而普通類型作為Receiver僅僅是以副本作為操作對象,并不對原實例對象發(fā)生操作。后文對此會有詳細(xì)論述。

那是不是method只能作用在struct上面呢?當(dāng)然不是咯,他可以定義在任何你自定義的類型、內(nèi)置類型、struct等各種類型上面。這里你是不是有點迷糊了,什么叫自定義類型,自定義類型不就是struct嘛,不是這樣的哦,struct只是自定義類型里面一種比較特殊的類型而已,還有其他自定義類型申明,可以通過如下這樣的申明來實現(xiàn)。

type typeName typeLiteral

請看下面這個申明自定義類型的代碼

type ages int

type money float32

type months map[string]int

m := months {
    "January":31,
    "February":28,
    ...
    "December":31,
}

看到了嗎?簡單的很吧,這樣你就可以在自己的代碼里面定義有意義的類型了,實際上只是一個定義了一個別名,有點類似于c中的typedef,例如上面ages替代了int

好了,讓我們回到method

你可以在任何的自定義類型中定義任意多的method,接下來讓我們看一個復(fù)雜一點的例子

package main
import "fmt"

const(
    WHITE = iota
    BLACK
    BLUE
    RED
    YELLOW
)

type Color byte

type Box struct {
    width, height, depth float64
    color Color
}

type BoxList []Box //a slice of boxes

func (b Box) Volume() float64 {
    return b.width * b.height * b.depth
}

func (b *Box) SetColor(c Color) {
    b.color = c
}

func (bl BoxList) BiggestColor() Color {
    v := 0.00
    k := Color(WHITE)
    for _, b := range bl {
        if bv := b.Volume(); bv > v {
            v = bv
            k = b.color
        }
    }
    return k
}

func (bl BoxList) PaintItBlack() {
    for i, _ := range bl {
        bl[i].SetColor(BLACK)
    }
}

func (c Color) String() string {
    strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
    return strings[c]
}

func main() {
    boxes := BoxList {
        Box{4, 4, 4, RED},
        Box{10, 10, 1, YELLOW},
        Box{1, 1, 20, BLACK},
        Box{10, 10, 1, BLUE},
        Box{10, 30, 1, WHITE},
        Box{20, 20, 20, YELLOW},
    }

    fmt.Printf("We have %d boxes in our set\n", len(boxes))
    fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm3")
    fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
    fmt.Println("The biggest one is", boxes.BiggestColor().String())

    fmt.Println("Let's paint them all black")
    boxes.PaintItBlack()
    fmt.Println("The color of the second one is", boxes[1].color.String())

    fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
}

上面的代碼通過const定義了一些常量,然后定義了一些自定義類型

  • Color作為byte的別名
  • 定義了一個struct:Box,含有三個長寬高字段和一個顏色屬性
  • 定義了一個slice:BoxList,含有Box

然后以上面的自定義類型為接收者定義了一些method

  • Volume()定義了接收者為Box,返回Box的容量
  • SetColor(c Color),把Box的顏色改為c
  • BiggestColor()定在在BoxList上面,返回list里面容量最大的顏色
  • PaintItBlack()把BoxList里面所有Box的顏色全部變成黑色
  • String()定義在Color上面,返回Color的具體顏色(字符串格式)

上面的代碼通過文字描述出來之后是不是很簡單?我們一般解決問題都是通過問題的描述,去寫相應(yīng)的代碼實現(xiàn)。

指針作為receiver

現(xiàn)在讓我們回過頭來看看SetColor這個method,它的receiver是一個指向Box的指針,是的,你可以使用*Box。想想為啥要使用指針而不是Box本身呢?

我們定義SetColor的真正目的是想改變這個Box的顏色,如果不傳Box的指針,那么SetColor接受的其實是Box的一個copy,也就是說method內(nèi)對于顏色值的修改,其實只作用于Box的copy,而不是真正的Box。所以我們需要傳入指針。

這里可以把receiver當(dāng)作method的第一個參數(shù)來看,然后結(jié)合前面函數(shù)講解的傳值和傳引用就不難理解

這里你也許會問了那SetColor函數(shù)里面應(yīng)該這樣定義*b.Color=c,而不是b.Color=c,因為我們需要讀取到指針相應(yīng)的值。

你是對的,其實Go里面這兩種方式都是正確的,當(dāng)你用指針去訪問相應(yīng)的字段時(雖然指針沒有任何的字段),Go知道你要通過指針去獲取這個值,看到了吧,Go的設(shè)計是不是越來越吸引你了。

也許細(xì)心的讀者會問這樣的問題,PaintItBlack里面調(diào)用SetColor的時候是不是應(yīng)該寫成(&bl[i]).SetColor(BLACK),因為SetColor的receiver是*Box,而不是Box。

你又說對的,這兩種方式都可以,因為Go知道receiver是指針,他自動幫你轉(zhuǎn)了。

也就是說:

如果一個method的receiver是*T,你可以在一個T類型的實例變量V上面調(diào)用這個method,而不需要&V去調(diào)用這個method

類似的

如果一個method的receiver是T,你可以在一個T類型的變量P上面調(diào)用這個method,而不需要 P去調(diào)用這個method

所以,你不用擔(dān)心你是調(diào)用的指針的method還是不是指針的method,Go知道你要做的一切,這對于有多年C/C++編程經(jīng)驗的同學(xué)來說,真是解決了一個很大的痛苦。

method繼承

前面一章我們學(xué)習(xí)了字段的繼承,那么你也會發(fā)現(xiàn)Go的一個神奇之處,method也是可以繼承的。如果匿名字段實現(xiàn)了一個method,那么包含這個匿名字段的struct也能調(diào)用該method。讓我們來看下面這個例子

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//在human上面定義了一個method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

method重寫

上面的例子中,如果Employee想要實現(xiàn)自己的SayHi,怎么辦?簡單,和匿名字段沖突一樣的道理,我們可以在Employee上面定義一個method,重寫了匿名字段的方法。請看下面的例子

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//Human定義method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重寫Human的method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

上面的代碼設(shè)計的是如此的美妙,讓人不自覺的為Go的設(shè)計驚嘆!

通過這些內(nèi)容,我們可以設(shè)計出基本的面向?qū)ο蟮某绦蛄耍荊o里面的面向?qū)ο笫侨绱说暮唵?,沒有任何的私有、公有關(guān)鍵字,通過大小寫來實現(xiàn)(大寫開頭的為公有,小寫開頭的為私有),方法也同樣適用這個原則。

上一篇:8.5 小結(jié)下一篇:2.6 interface