聊聊 Go 語言中的面向對象編程

咱們知道,在 Go 語言中沒有類(Class)的概念,但這並不意味着 Go 語言不支持面向對象編程,畢竟面向對象只是一種編程思想。git

讓咱們回憶一下面向對象的三大基本特徵:github

  1. 封裝:隱藏對象的屬性和實現細節,僅對外提供公共訪問方式
  2. 繼承:使得子類具備父類的屬性和方法或者從新定義、追加屬性和方法等
  3. 多態:不一樣對象中同種行爲的不一樣實現方式

咱們一塊兒來看看 Go 語言是如何在沒有類(Class)的狀況下實現這三大特徵的。編程

封裝

「類」

在 Go 語言中可使用結構體Structs)對屬性進行封裝,結構體就像是類的一種簡化形式。數組

例如,咱們要定義一個矩形,每一個矩形都有長和寬,咱們能夠這樣進行封裝:編程語言

type Rectangle struct {
	Length int
	Width int
}
複製代碼

方法

既然有了「類」,你可能會問了,那「類」的方法在哪呢?ide

Go 語言中也有方法Methods):Go 方法是做用在接收者(receiver)上的一個函數,接收者是某種類型的變量。所以方法是一種特殊類型的函數。函數

定義方法的格式以下:post

func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
複製代碼

上文中咱們已經定義了一個矩形 Rectangle,如今咱們要定義一個方法 Area() 來計算它的面積:ui

package main

import (
	"fmt"
)

// 矩形結構體
type Rectangle struct {
	Length int
	Width  int
}

// 計算矩形面積
func (r *Rectangle) Area() int {
	return r.Length * r.Width
}

func main() {
	r := Rectangle{4, 2}
	// 調用 Area() 方法,計算面積
	fmt.Println(r.Area())
}
複製代碼

上面的代碼片斷輸出結果爲 8。spa

訪問權限

咱們常會說一個類的屬性是公共的仍是私有的,在其餘編程語言中,咱們經常使用 publicprivate 關鍵字來表達這樣一種訪問權限。

在 Go 語言中沒有 publicprivateprotected 這樣的訪問控制修飾符,它是經過字母大小寫來控制可見性的。

若是定義的常量、變量、類型、接口、結構、函數等的名稱是大寫字母開頭,這表示它們能被其它包訪問或調用(至關於 public);非大寫開頭就只能在包內使用(至關於 private)。

訪問未導出字段

當遇到只能在包內使用的未導出字段時,咱們又該如何訪問呢?

和其餘面嚮對象語言同樣,Go 語言也有實現 gettersetter 的方式:

  • 對於 setter 方法使用 Set 前綴
  • 對於 getter 方法只使用成員名

例如咱們如今有一個處於 person 包中的 Person 結構體:

package person

type Person struct {
	firstName string
	lastName  string
}
複製代碼

咱們能夠看到,它的兩個成員變量都是非大寫字母開頭,只能在包內使用,如今咱們爲其中的 firstName 來定義 settergetter

// 獲取 firstName
func (p *Person) FirstName() string {
	return p.firstName
}

// 設置 firstName
func (p *Person) SetFirstName(newName string) {
	p.firstName = newName
}
複製代碼

這樣一來,咱們就能夠在 main 包裏設置和獲取 firstName 的值了:

package main

import (
	"fmt"

	"./person"
)

func main() {
	p := new(person.Person)
	p.SetFirstName("firstName")
	fmt.Println(p.FirstName())
}

/* Output: firstName */
複製代碼

繼承

在 Go 語言中沒有 extends 關鍵字,它使用在結構體中內嵌匿名類型的方法來實現繼承。

匿名類型:即這些類型沒有顯式的名字。

咱們定義一個 Engine 接口類型,一個 Car 結構體,讓 Car 結構體包含一個 Engine 類型的匿名字段:

type Engine interface {
	Start()
	Stop()
}

type Car struct {
	Engine // 包含 Engine 類型的匿名字段
}
複製代碼

此時,匿名字段 Engine 上的方法「晉升」成爲了外層類型 Car 的方法。咱們能夠構建出以下代碼:

func (c *Car) GoToWorkIn() {
	// get in car
	c.Start()
	// drive to work
	c.Stop()
	// get out of car
}
複製代碼

多態

在面向對象中,多態的特徵爲:不一樣對象中同種行爲的不一樣實現方式。在 Go 語言中可使用接口實現這一特徵。

咱們先定義一個正方形 Square 和一個長方形 Rectangle

// 正方形
type Square struct {
	side float32
}

// 長方形
type Rectangle struct {
	length, width float32
}
複製代碼

而後,咱們但願能夠計算出這兩個幾何圖形的面積。但因爲他們的面積計算方式不一樣,咱們須要定義兩個不一樣的 Area() 方法。

因而,咱們能夠定義一個包含 Area() 方法的接口 Shaper,讓 SquareRectangle 都實現這個接口裏的 Area()

// 接口 Shaper
type Shaper interface {
	Area() float32
}

// 計算正方形的面積
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

// 計算長方形的面積
func (r *Rectangle) Area() float32 {
	return r.length * r.width
}
複製代碼

咱們能夠在 main() 函數中這樣調用 Area()

func main() {
	r := &Rectangle{10, 2}
	q := &Square{10}

	// 建立一個 Shaper 類型的數組
	shapes := []Shaper{r, q}
	// 迭代數組上的每個元素並調用 Area() 方法
	for n, _ := range shapes {
		fmt.Println("圖形數據: ", shapes[n])
		fmt.Println("它的面積是: ", shapes[n].Area())
	}
}

/*Output: 圖形數據: &{10 2} 它的面積是: 20 圖形數據: &{10} 它的面積是: 100 */
複製代碼

由以上代碼輸出結果可知:不一樣對象調用 Area() 方法產生了不一樣的結果,展示了多態的特徵。

總結

  • 面向對象的三大特徵是:封裝、繼承和多態
  • Go 語言使用結構體對屬性進行封裝,結構體就像是類的一種簡化形式
  • 在 Go 語言中,方法是做用在接收者(receiver)上的一個函數,接收者是某種類型的變量
  • 名稱首字母的大小寫決定了該變量/常量/類型/接口/結構/函數……可否被外部包導入
  • 沒法被導入的字段可使用 gettersetter 的方式來訪問
  • Go 語言使用在結構體中內嵌匿名類型的方法來實現繼承
  • 使用接口能夠實現多態

參考資料


往期回顧


🐱

若是你以爲文章寫得不錯,請幫我兩個小忙:

  1. 點贊並關注我,讓這篇文章被更多人看到
  2. 關注公衆號「編程拯救世界」,公衆號專一於編程基礎與服務端研發,你將第一時間得到新文章的推送~

原創不易,多多鼓勵~謝謝你們!

相關文章
相關標籤/搜索