1. 程式人生 > 實用技巧 >【go語言學習】面向物件oop

【go語言學習】面向物件oop

go並不是一個純面向物件的程式語言。在go中的面向物件,結構體替換了類。
go並沒有提供類class,但是它提供了結構體struct,方法method可以在結構體上新增。提供了捆綁資料和方法的行為,這些資料和方法與類類似。

面向物件的基本思想主要體現在封裝,繼承以及多型等的設計與運用上。下面來看看封裝、繼承與多型在golang中是如何實現的。

一、封裝

  • 封裝主要是通過訪問許可權控制實現的。
  • 在Java中,共有public 、protected、default、private這四種許可權控制。
  • 而相應的在golang中,是通過約定來實現許可權控制的。變數名首字母大寫,相當於java中的public,首字母小寫,相當於private。同一個包中訪問,相當於default。由於go沒有繼承,也就沒有protected。

在go_project目錄下建立model檔案,在model資料夾下建立student.go

package model
// student 學生結構體,首字母小寫,包外不可見
type student struct {
	Name string //  首字母大寫,包外可見
	age  int  // 首字母小寫,包外不可見
}

// New student的建構函式
func New(name string, age int) *student {
	return &student{
		Name: name,
		age:  age,
	}
}

// GetAge student的方法,用於外部包訪問age欄位
func (s *student) GetAge() int {
	return s.age
}

// SetAge student的方法,用於外部包修改age欄位
func (s *student) SetAge(age int) {
	s.age = age
}

在go_project目錄下建立main資料夾,在main資料夾下建立main.go

package main

import (
	"fmt"
	"go_project/model"
)

func main() {
	s := model.New("tom", 20)
	fmt.Println(s)
	// 可以直接訪問Name欄位
	fmt.Println(s.Name)
	// 訪問不到age欄位
	// fmt.Println(s.age)
	fmt.Println(s.GetAge())
	s.Name = "jack"
	// 無法直接修改age欄位
	// s.age = 23
	s.SetAge(23)
	fmt.Println(s)
}

執行結果

&{tom 20}
tom
20
&{jack 23}
  • 在model包裡面定義了一個student的結構體;由於首字母小寫,所以不可能從其他包中建立student結構體的例項。也就是說沒有其他的包能夠建立一個零值的student例項。
  • 現在匯出student例項只能通過建構函式NewT(parameters)。如果包只定義了一個型別,那麼約定將這個函式命名為New(parameters)而不是NewT(parameters)。
  • 由於沒有將student的所有欄位匯出,避免了對student欄位的隨意訪問和修改。只能通過匯出的student的GetSet方法操作。

二、繼承

go不支援繼承,可以通過將一個struct型別嵌入到另一個結構中實現類似繼承效果。

package main

import "fmt"

// Person 結構體 父類
type Person struct {
	name string
	age  int
}

// Say Person的方法,父類方法
func (p Person) Say() {
	fmt.Println(p.name, "say hello")
}

// Student 結構體 子類
type Student struct {
	Person
	school string
}

// Study Student的方法, 子類自己的方法
func (s Student) Study() {
	fmt.Println(s.name, "goood good study, day day up")
}

// Say Student的方法,重寫父類的方法
func (s Student) Say() {
	fmt.Println(s.name, "說:你好!")
}

func main() {
	// 建立父類物件
	p := Person{
		name: "tom",
		age:  19,
	}
	p.Say()
	// 建立子類物件
	s := Student{
		Person: Person{
			name: "jack",
			age:  18,
		},
		school: "清華大學",
	}
	// 訪問父類的方法
	s.Say()
	// 訪問子類自己的方法
	s.Study()
}

執行結果

tom say hello
jack 說:你好!
jack goood good study, day day up

三、多型

Java 中的多型是通過 extends class 或者 implements interface 實現的,在 golang 中既沒有 extends,也沒有 implements ,那麼 go 中多型是如何實現的呢 ?

答案:在golang中,只要某個struct實現了某個interface中的所有方法,那麼我們就認為,這個struct實現了這個介面。

介面型別的變數可以儲存實現介面的任何值。介面的這個屬性用於實現Go中的多型性。

一個介面的實現:

  • 看成實現本身的型別,可以訪問實現類中的屬性和方法
  • 看成對應的介面型別,只能訪問介面中的方法
package main

import "fmt"

// AnimalIF 介面
type AnimalIF interface {
	Eat()
	Sleep()
}

// Animal 結構體 父類
type Animal struct {
	name string
	age  int
}

// Eat Animal的方法
func (a Animal) Eat() {
	fmt.Println(a.name, "eat")
}

// Sleep Animal的方法
func (a Animal) Sleep() {
	fmt.Println(a.name, "sleep")
}

// Cat 結構體 子類
type Cat struct {
	Animal
	color string
}

// Eat Cat的方法,重寫父類的方法
func (c Cat) Eat() {
	fmt.Println("cat eat fish")
}

// Dog 結構體 子類
type Dog struct {
	Animal
}

// LookDoor Dog的方法, 子類自己的方法
func (d Dog) LookDoor() {
	fmt.Println("dog lookdoor")
}

func main() {
	// 建立父類物件
	a := Animal{
		name: "獅子",
		age:  5,
	}
	a.Eat()
	// 建立子類物件
	c := Cat{
		Animal: Animal{
			name: "小花",
			age:  3,
		},
		color: "白色",
	}
	// 建立子類物件
	d := Dog{
		Animal: Animal{
			name: "旺財",
			age:  2,
		},
	}
	// 訪問子類擁有的父類的欄位
	fmt.Println(c.name, c.age)
	// 訪問子類自己的欄位
	fmt.Println(c.color)
	// 訪問父類的方法
	d.Eat()
	// 訪問子類自己的方法
	d.LookDoor()
	// 訪問子類重寫的父類的方法
	c.Eat()
	fmt.Println("--------------------")

	// 建立介面型別
	var ai AnimalIF
	ai = a
	ai.Eat()
	ai.Sleep()
	ai = c
	ai.Eat()
	ai.Sleep()
	ai = d
	ai.Eat()
	ai.Sleep()
}

執行結果

獅子 eat
小花 3
白色
旺財 eat
dog lookdoor
cat eat fish
--------------------
獅子 eat
獅子 sleep
cat eat fish
小花 sleep
旺財 eat
旺財 sleep

應用舉例
計算一些圖形的面積,目前有正方形和圓形。

package main

import (
	"fmt"
	"math"
)

// Area 面積介面,包含兩個方法source和calculate
type Area interface {
	source() string
	calculate() float64
}

// Square 結構體,正方形
type Square struct {
	graphName  string
	sideLength float64
}

// source Square的方法
func (s Square) source() string {
	return s.graphName
}

// calculate Square的方法
func (s Square) calculate() float64 {
	return math.Pow(s.sideLength, 2)
}

// Round 結構體,圓形
type Round struct {
	graphName string
	radius    float64
}

// source Round的方法
func (r Round) source() string {
	return r.graphName
}

// calculate Round的方法
func (r Round) calculate() float64 {
	return math.Pi * math.Pow(r.radius, 2)
}

// totalArea 獲取總面積的函式
func totalArea(a []Area) {
	var totalArea = 0.0
	for _, v := range a {
		fmt.Printf("面積來自%v, 它的面積是%v\n", v.source(), v.calculate())
		totalArea += v.calculate()
	}
	fmt.Printf("所有圖形的總面積是:%v\n", totalArea)
}

func main() {
	graph1 := Square{
		graphName:  "graph1",
		sideLength: 6.3,
	}
	graph2 := Round{
		graphName: "graph2",
		radius:    3.4,
	}
	a := []Area{graph1, graph2}
	totalArea(a)
}

執行結果

面積來自graph1, 它的面積是39.69
面積來自graph2, 它的面積是36.316811075498
所有圖形的總面積是:76.006811075498

如果再增加長方形的面積,不需要對totalArea()做任何更改。

首先定義Rectangle結構體和source()calculate()方法

// Rectangle 結構體, 長方形
type Rectangle struct {
	graphName string
	length    float64
	width     float64
}

// source Rectangle的方法
func (r Rectangle) source() string {
	return r.graphName
}

// calculate Rectangle的方法
func (r Rectangle) calculate() float64 {
	return r.length * r.width
}

修改主函式

func main() {
	graph1 := Square{
		graphName:  "graph1",
		sideLength: 6.3,
	}
	graph2 := Round{
		graphName: "graph2",
		radius:    3.4,
	}
	graph3 := Rectangle{
		graphName: "graph3",
		length:    12.3,
		width:     6.5,
	}
	a := []Area{graph1, graph2, graph3}
	totalArea(a)
}

執行結果

面積來自graph1, 它的面積是39.69
面積來自graph2, 它的面積是36.316811075498
面積來自graph3, 它的面積是79.95
所有圖形的總面積是:155.95681107549802