1. 程式人生 > >Golang中的面向物件程式設計

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#介面的變體,而且介面間是不相關的,並且是大規模程式設計和可適應的演進型設計的關鍵。