Golang中的面向物件程式設計
Go語言中的面向物件程式設計
Golang面向物件
結構體和方法
定義方法時需要了解:
func createNode(value int) *Node {
return &Node{value : value}
}
- Go 語言中沒有構造和解構函式,因此一般都是通過普通函式來作為工廠函式建立結構體
- 注意該函式返回了局部變數的地址,這在Go語言中是允許的。
- 此時就需要考慮該區域性變數是存在棧上還是堆上?
記憶體分配的位置是有編譯器和執行環境決定的,在本環境中返回區域性變數和返回區域性變數的地址均可(經過測試)。
定義方法時需要注意:
func (node *Node) SetValue (value int) {
if nil == node {
fmt.Println("Setting value to nil node, ignored!")
return
}
node.value = value
}
func (node Node) Print() {
fmt.Print(node.value, " ")
}
- 要改變結構內容必須使用指標接收者,值接收者是Go語言特有的。
- 結構過大也要考慮使用指標接收者,避免值接收者在拷貝時的花銷過大。
- 考慮到一致性,如有指標接收者,最好都是指標接收者
使用方法時需要注意:
var pRoot *Node
pRoot. SetValue(2)
- nil指標也可以呼叫方法,但是nil指標指向不合法地址。
- 無論是值接收者和指標接收者,它們呼叫方法的方式都是一樣的
示例
package main
import "fmt"
type Node struct {
value int
left, right *Node
}
func (node *Node) SetValue(value int) {
if nil == node {
fmt.Println("Setting value to nil node, ignored!")
return
}
node.value = value
}
func createNode(value int) *Node {
return &Node{value : value}
}
func (node Node) Print() {
fmt.Print(node.value, " ")
}
func (node *Node) traverse() {
if node == nil {
return
}
if node.left != nil {
node.left.traverse()
}
node.Print()
if node.right != nil {
node.right.traverse()
}
}
func main() {
var root Node
root = Node{value: 3}
root.left = &Node{}
root.right = &Node{5, nil, nil}
root.right.left = new(Node)
root.left.right = createNode(2)
root.right.left.SetValue(4)
var pRoot *Node
pRoot.SetValue(2)
root.traverse()
}
封裝
- 包名一般採用CamelCase,首字母大寫連在一起
- 首字母大寫:public
- 首字母小寫:private,public和private都是相對於包來講的
- 在Go語言中,每個目錄為一個包,包名可以和目錄名不一樣,main包包含了程式執行入口(main函式)
- 為結構定義的所有方法,必須放在同一個包內,可以是不同的檔案
擴充套件已有的型別
1.通過組合的方式
type myTreeNode struct {
node *tree.Node
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
right := myTreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
myNode.node.Print()
}
2.通過別名的方式
package queue
// 定義了新型別Queue,該型別具有幾種方法
type Queue []int
// 因為需要改引數,所以傳地址
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0] // 注意加括號
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool{
return len(*q) == 0
}
Golang面向介面
介面的定義和使用
- 介面的實現是隱式的,只需要結構實現了介面的方法,我們就說實現了這個介面。
在main包中我們定義了一個Retriever介面,裡面包含一個Get方法。在mock包中我們定義一個Retriever結構體,實現了Get方法,我們就當做mock.Retriever實現了Retriever介面。
package main
...
type Retriever interface {
Get(url string) string
}
...
package mock
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
- 介面變數自帶指標,指的是介面的實現時,接收者為指標型別
- 介面變數也是值傳遞,幾乎不需要使用介面的指標
- 指標接收者實現只能以指標方式使用;值接收者既可以使用指標方式,也可以使用值方式。
在real包中我們定義Retriever結構實現了介面Retriever,實現了Get方法,並將其接收者定義為指標。
package real
import (
"time"
"net/http"
"net/http/httputil"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r *Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
bytes, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(bytes)
}
在定義real.Retriever結構中的方法Get時,採用的指標接收者,因此在使用時只能以指標方式使用,如下所示
func download(r Retriever) string {
return r.Get("http://www.imooc.com")
}
var r Retriever
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
fmt.Printf("%T, %v\n", r, r)
fmt.Println(download(r))
而定義mock.Retriever結構中的Get方法時,採用的值接收者,在使用時,既可以採用值方式使用也可以使用指標方式使用,如下所示:
值傳遞方式
var r Retriever
r = mock.Retriever{"This is a mock retriever!"}
fmt.Printf("%T, %v\n", r, r)//mock.Retriever, &{This is a mock retriever!}
fmt.Println(download(r))
指標傳遞方式
var r Retriever
r = &mock.Retriever{"This is a mock retriever!"}
fmt.Printf("%T, %v\n", r, r)//*mock.Retriever, &{This is a mock retriever!}
fmt.Println(download(r))
Golang中的“多型”
- Go語言中的任意型別: interface{}
- Type Assertion
- Type Switch
可以通過 type Assertion 和 Type Switch 來判斷介面變量表示的實體型別
Type Assertion
//Type assertion
if realRetriever, ok := r.(*real.Retriever); ok {
fmt.Println(realRetriever.UserAgent)
} else {
fmt.Println("not a real retriever!")
}
Type Switch
func inspect(r Retriever) {
switch v := r.(type) {
case mock.Retriever:
fmt.Println("contents", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent: ", v.UserAgent)
}
}
介面的組合
一個介面可以包含一個或多個其他的介面,這相當於直接將這些內嵌介面的方法列舉在外層介面中一樣。
比如介面 File 包含了 ReadWrite 和 Lock 的所有方法,它還額外有一個 Close() 方法。
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
總 結
我們總結一下前面看到的:Go沒有類,而是鬆耦合的型別、方法對介面的實現。
OO 語言最重要的三個方面分別是:封裝,繼承和多型,在 Go 中它們是怎樣表現的呢?
封裝(資料隱藏):和別的 OO 語言有 4 個或更多的訪問層次相比:
- 包範圍內的:通過識別符號首字母小寫,物件 只在它所在的包內可見
- 可匯出的:通過識別符號首字母大寫,物件 對所在包以外也可見
型別只擁有自己所在包中定義的方法。
繼承:用組合實現:內嵌一個(或多個)包含想要的行為(欄位和方法)的型別;多重繼承可以通過內嵌多個型別實現
多型:用介面實現:某個型別的例項可以賦給它所實現的任意介面型別的變數。型別和介面是鬆耦合的,並且多重繼承可以通過實現多個介面實現。Go介面不是java和C#介面的變體,而且介面間是不相關的,並且是大規模程式設計和可適應的演進型設計的關鍵。