文章目錄
1. GO語言OOP概述
- Go語言不是純粹的面向對象的語言,準確是描述是,Go語言支持面向對象編程的特性.
- Go語言中沒有傳統的面向對象編程語言的
class
,而Go語言中的struct
和 其他編程語言中的class
具有同等地位,也就是說Go語言是 基於struct
來實現OOP
的特性的- 面向對象編程在Go語言中的支持設計得很具有特點,Go語言放棄了很多面向對象編程的概念,如
繼承
,重載
,構造函數
,析構函數
,隱藏this指針
等- Go語言可以實現面向對象編程的特性
封裝
,繼承
,多態
- Go語言面向對象編程的支持是
語言類型系統
中天然的組成部分,整個類型系統通過接口串聯,靈活度高
面向對象編程OOP的三大基本特徵是
封裝
繼承
和多態
Go語言支持面向對象編程,但是它實現面向對象的方式與其他語言不同,Go語言中沒有private
,protected
,public
等對成員屬性或者方法進行可見性描述的關鍵字 , 沒有繼承
這個字面的概念也不存在多繼承
的概念等等,但是Go語言通過自身語言特點實現了對OOP的支持,關於這點我們看到在一些資料中提到了duck typing
的概念,When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck
一隻鳥走路像鴨子,游泳像鴨子叫聲也像鴨子,那我們就可以稱它是鴨子通過對Go語言的學習,一些我們很模糊的概念都會清晰的
2. 封裝的實現
封裝是將抽象出來的字段或者屬性和對字段屬性的操作封裝在一起,其他程序只能通過授權的方法才能對其進行操作,Go語言在開發中不是很強調封裝的特性.無需糾結與其他語言不同的方面.
封裝的實現
- 包內成員入定義的變量或者方法或者函數首字母小寫,僅在包內可見,包外不可見
- 結構體,字段屬性的的首字母小寫決定其對外不可見
- 爲結構體所在的包提供一個
構造函數
- 提供一些可以設置
私有屬性的方法
對外可見- 提供一些可以獲取屬性的方法對外可見
示例
// 文件結構如下
|__model
|____person.go
|__main.go
person.go
package model
// 這是一`私有`person 類型結構體
// 包外不能直接生生成一個該類型的實例
// person 首字母小寫包外不見
type person struct {
// 首字母大寫保外可見
Name string
// 首字母小寫保外不可見
age int8
Work string
deposit float64
hobbys []string
}
// 構造函數
// 包外可見
func NewPerson(name string, age int8, work string, deposit float64) *person {
return &person{
Name: name,
age: age,
Work: work,
deposit: deposit,
}
}
// 給person類型數據添加各種方法
func (p *person) GetAge() int8 {
return p.age
}
func (p person) GetDeposit() float64 {
return p.deposit
}
func (p *person) SetHobby(h string) {
p.hobbys = append(p.hobbys, h)
}
func (p person) GetHobbys() []string {
return p.hobbys
}
main.go
package main
import (
"GoNote/chapter6/demo18/model"
"fmt"
)
func main(){
// 定義一個結構體變量p1
p1 := model.NewPerson("tom",26,"UI",50000.01)
fmt.Println(p1)
// 只能訪問可見屬性, 成員屬性age和deposit是訪問不到的
fmt.Println(p1.Name,p1.Work)
// 通過可見方法訪問不可見的屬性
fmt.Println(p1.GetAge())
fmt.Println(p1.GetDeposit())
// 調用設置的方法
(*p1).SetHobby("跑步")
p1.SetHobby("爬山")
// 嗲用獲取的方法
fmt.Println(p1.GetHobbys())
}
go run main.go
&{tom 26 UI 50000.01 []}
tom UI
26
50000.01
[跑步 爬山]
3. 繼承的實現
Go語言的繼承是通過組合的方式實現的.
- 結構體中內嵌了只有類型沒有名稱的結構體的時候我們稱之爲內嵌了
匿名結構體
- 結構體總內嵌的結構體既有名字也有類型,這種模式我們稱爲
組合
- Go語言的結構體內嵌特性就是一種組合,使用組合可以快速構建對象的不同屬性
package main
import "fmt"
// 定義一個Good結構體
type Good struct {
name string
productDate string
price float64
}
// 定義一個Books結構體
type Books struct {
// 內嵌Good類型的結構體
Good
// Books自由的成員字段
classify string
publishing string
author string
}
// 定義Vendor結構體
type Vendor struct {
VendorName string
retailPrice float64
grade float32
}
// 定義ProgrammingBook 的結構體
type ProgrammingBook struct {
// 構成組合
// 此處類似於多繼承
b Books
v Vendor
overview string
chapters []string
}
// 給Good類型綁定一個方法
func (g *Good) setGoodInfo(name string,pd string,price float64) {
g.name = name
g.productDate = pd
g.price = price
}
func (g *Good) GoodDescribe() string {
return fmt.Sprintf("商品名稱是%s,價格是%.2f,生產日期是%s\n", g.name, g.price, g.productDate)
}
// 給Books綁定一個BasicInfo的方法
func (b *Books) booksDescribe() string {
return fmt.Sprintf("書的分類是%s,出版商是%s,作者是%s\n", b.classify, b.publishing, b.author)
}
func (p *ProgrammingBook) GetBook() {
//可以調用
ginfo := p.b.GoodDescribe()
binfo := p.b.booksDescribe()
fmt.Printf("商品信息 :%s 書籍信息 :%s 書籍概覽 :%s",ginfo,binfo,p.overview)
}
func main() {
var gobook ProgrammingBook
// 結構體變量Gobook 內嵌了 Books類型結構體,Books結構體又和Good是組合的
//不是很嚴謹的說法:相當於ProgrammingBook 繼承 Books ,Books繼承了Good , 那麼ProgrammingBook也繼承Good的屬性和方法
// 所以ProgrammingBook的實例 gobook能調用Good的方法
gobook.b.setGoodInfo("Go程序設計語言","2019-10-01",99.90)
// ProgrammingBook結構體內嵌了有名結構體Books,Vendor(組合),相當於繼承了他們屬性和方法
gobook.b.classify = "計算機|編程"
gobook.b.publishing = "機械工業出版社"
gobook.b.author = "艾倫 A.A.多諾萬"
gobook.v.VendorName = "噹噹網"
gobook.v.retailPrice = 89.0
gobook.v.grade = 9.2
gobook.overview = "號稱Go語言界的聖經"
gobook.GetBook()
}
go run main.go
商品信息 :商品名稱是Go程序設計語言,價格是99.90,生產日期是2019-10-01
書籍信息 :書的分類是計算機|編程,出版商是機械工業出版社,作者是艾倫 A.A.多諾萬
書籍概覽 :號稱Go語言界的聖經
對結構體組合的補充描述
- 無論匿名的內嵌的匿名結構體還是組合,各個結構體中有相同的結構體字段,那麼訪問遵循
就近原則
, 就近原則無效的時候,匿名內嵌的必須通過結構體類型訪問該字段,組合的必須通過 結構體名(類似別名),去訪問- 給不同的類型添加方法時方法名可以相同.前提是接收器不能相同
- 結構體內嵌多個結構體會出現
多繼承
的特點- 出現多層級的內嵌結構體如 : A內嵌在B中,B內嵌在C中,C內嵌在D中,D內嵌在E中 … 無論是想訪問結構體屬性(字段) 或者是調用方法,只需要正確的指向那個結構體,就可訪問成員屬性和調用方法
package main
import "fmt"
type AA struct {
name string
AAAddr string
}
type BB struct {
name string
BBAddr string
}
type DD struct {
name string
DDAddr string
}
type XX struct {
AA
name string
XXAddr string
}
type CC struct {
AA
BB
XX
d DD
name string
}
// 給AA結構體類型添加方法demo1
func (a *AA) demo1(){
fmt.Println(a.name)
}
// 給DD結構體類型添加方法demo1
func (d *DD) demo1(){
fmt.Println(d.name)
}
// 給CC結構體類型添加方法demo1
func (c *CC) demo1(){
fmt.Println(c.name)
}
func main(){
ins1 := new(CC)
// 此時訪問的是CC結構體自己字段name(就近原則)
ins1.name = "Name-CC"
// CC 結構體內嵌了匿名結構體AA,CC,他們的name在邏輯山處於同一層級
// 要訪問AA或者BB中的name 那就必須帶上結構類型名
ins1.AA.name = "Name-AA"
// 多層級內嵌匿名結構體,訪問方式也是逐層範文過去
ins1.XX.AA.name = "XX(Name-AA)"
// DD是CC有名結構體內嵌,或者叫組合
ins1.d.name = "Name-DD"
// 匿名結構體並且成員字段是唯一的,可以這樣直接訪問
ins1.BBAddr = "addr of BB"
// 組合類型的 有名內嵌那就必須帶上名稱
ins1.d.DDAddr = "addr of DD"
// 調用的是自己的方法 CC 結構體綁定的方法
ins1.demo1()
// 調用組合DD的demo1方法
ins1.d.demo1()
// 調用內嵌的匿名結構體AA的demo1方法
ins1.AA.demo1()
ins1.XX.demo1()
}
go run main.go
Name-CC
Name-DD
Name-AA
XX(Name-AA)
4. 多態的實現
多態
是指代碼根據類型的具體實現採取不同行爲的能力在Go語言中多態的特徵是通過接口
interface
來體現的
4.1 接口概述
我們將
接口
看着一種雙方約定的協議, 接口的實現者不用在意接口會被怎樣使用,接口的調用者不用在關心接口內部的實現細節.Go語言中接口也是一種類型,用來定義行爲,也是一種抽象結構.被定義的行爲不是由接口直接實現,而是通過方法由用戶定義的類型實現,用戶定義的類型實現了接口類型聲明的一組方法,那麼該用戶類型就實現了該接口,同時用戶定義的類型變量就可以賦給該接口類型變量,該過程會將用戶定義的類型變量存入接口類型變量中,接口變量對接口定義方法的調用會執行存入的用戶定義類型變量對方法的調用,此時就的調用就是一種多態
Go語言中的接口設計是 非侵入式 的, 其具體變現特徵是 : 接口的定義者無需知道接口被哪些類型實現了,而接口的實現者也不需要知道實現了哪些接口,無需指明已經實現了哪些接口,只需要關注自己實現的是什麼樣的接口即可.編譯器會自己識別哪個類型實現哪些接口
侵入式 主要體現是實現接口的類需要很明確的聲明自己實現了哪個接口
4.2 聲明接口
聲明接口的格式如下
- 關鍵字
type
和interface
- 接口類型名 是自定義的,命名方式和正常的變量名相同,爲了嚴格起見通常會在接口類型名後面追加
er
- 函數名 和 接口類型名 的首字母大小寫決定了接口和它定義的方法包外可見性
- 參數列表和返回值列表的定義和普通函數中的參數列表和返回值列表類似
- 接口中的參數列表和放回值列表中的變量名均可以忽略
- 接口中所有的方法都沒有方法體
- 接口中不能定義任何變量
package main
type 接口類型名 interface {
函數名1(參數列表1) 返回值列表1
函數名2(參數列表2) 返回值列表2
函數名3(參數列表3) 返回值列表3
函數名4(參數列表4) 返回值列表4
函數名5(參數列表5) 返回值列表5
}
func main(){
}
package main
type Demoer interface {
func1(int,string) (int ,string)
func2(int,int) (int ,error)
func3(s1 ,s2 string) (s3 string ,err error)
func4()
}
func main(){
}
4.3 實現接口
接口的實現由兩個基本原則
- 接口的方法和實現接口的類型的方法格式必須完全一致
- 接口中的所有方法都被實現了才能說接口被實現了
package main
import "fmt"
type Doer interface {
Music()
ShowTv()
}
// 實現了接口Doer
type MP4 struct {
}
func (m MP4) Music() {
fmt.Println("play music")
}
func (m MP4) ShowTv() {
fmt.Println("play Tv")
}
func main() {
var m = new(MP4)
m.Music()
m.ShowTv()
}
一個類型可以實現多個接口
一個接口可以被多個類型實現
package main
import "fmt"
// Volume類型 實現CommonFunc接口
type Volume int
type BasicFunc interface {
Start()
Stop()
}
// 接口 CommonFunc 被兩種數據類型實現
// 多種數據類型可以實現相同的接口
type CommonFunc interface {
VolumeIncrease()
VolumeReduce()
}
// TV類型結構體實現了CommonFunc和BasicFunc接口
type TV struct {
}
func (v Volume) VolumeIncrease() {
fmt.Println("音量增加")
}
func (v Volume) VolumeReduce() {
fmt.Println("音量減少")
}
func (t TV) Start() {
fmt.Println("開機")
}
func (t TV) Stop() {
fmt.Println("關機")
}
func (t TV) VolumeIncrease() {
fmt.Println("音量增加")
}
func (t TV) VolumeReduce() {
fmt.Println("音量減少")
}
func main() {
}
4.4 接口嵌套
Go語言中,接口與接口之間可以通過嵌套創造出新的接口
嵌套產生的新接口要被實現那麼它嵌套關聯的接口都被實現纔行
簡單講 : X接口 是可以繼承多個接口 如A接口 B接口 ,如果想實現X接口,就必須實現A,B接口中的所有方法
package main
// A 接口被Demo類型事項
type A interface {
FuncA()
}
// B 接口被Demo類型實現
type B interface {
FuncB()
}
// x 接口被Demo類型實現
type X interface {
A
B
}
type Demo struct {
}
func (a Demo) FuncA() {
}
func (b Demo) FuncB() {
}
func main() {
}
4.5 類型斷言的格式
類型斷言的格式如下:
t := i.(T) // t表示轉換之後的變量 // i表示接口變量 // T表示轉換的目標類型
接口類型可以接受任何的的數據,但是還是不知道到時什麼類型,所以需要使用類型斷言
package main
import "fmt"
func main(){
// 定義接口類型變量 i
var i interface{}
// 定義浮點類型變量f
var f float64 = 98.90
// 因爲i是空接口類型所以可以接受如何變量
i = f
x := i.(float64)
fmt.Printf("x type = %T,value = %0.2f",x,x)
}
go run main.go
x type = float64,value = 98.90
類型斷言的如果不匹配就會報panic
這時就需要在類型斷言的時候帶上檢測機制
基礎格式如下
t,ok := i.(T) // 和上述的類型斷言格式一樣 // ok 表示類型匹配是否成功的的標識,ok的類型是bool, 值爲true表示成功,false表示失敗 ok這個變量名是慣例寫法,變量名可以自定義
package main
import "fmt"
func main() {
// 定義接口類型變量 i
var i interface{}
// 定義浮點類型變量f
var f float64 = 98.90
// 因爲i是空接口類型所以可以接受如何變量
i = f
x := i.(float64)
fmt.Printf("x type = %T,value = %0.2f\n", x, x)
y ,ok:= i.(float32)
fmt.Printf("ok type = %T,value = %v\n", ok, ok)
if ok{
fmt.Printf("x type = %T,value = %0.2f\n", y, y)
}else{
fmt.Println("類型不符合")
}
}
go run main.go
x type = float64,value = 98.90
ok type = bool,value = false
類型不符合
Go語言中的switch可以做的類型斷言
基本格式 :
switch 任意類型變量.(type){ case 類型1: 處理邏輯1 case 類型2: 處理邏輯2 ... default: 處理邏輯n }
package main
import "fmt"
type Demo1 struct {
data map[string]string
}
type Demo2 struct {
}
func PrintType (v interface{}) {
switch v.(type) {
case int :
fmt.Println(v,"is int")
case string :
fmt.Println(v,"is string")
case bool :
fmt.Println(v ,"is bool")
case Demo1:
fmt.Println(v,"is Demo1")
case Demo2 :
fmt.Println(v,"is Demo2")
}
}
func main(){
PrintType(99)
PrintType("golang")
PrintType(true)
demo1 := Demo1{data: map[string]string{"name":"tom"}}
PrintType(demo1)
demo2 := Demo2{}
PrintType(demo2)
}
go run main.go
99 is int
golang is string
true is bool
{map[name:tom]} is Demo1
{} is Demo2
4.6 空接口類型 interface{}
空接口是接口類型的特殊形式, 空接 口沒有任何方法,因此任何類型都無須實現空接口,從實現的角度看,任何值都滿足這個接口的需求
- 空接口也是一種類型
- 空間接口可以接受任何值
package main
import "fmt"
// 聲明一個interface{}類型變量any
var any interface{}
func main() {
// 將int型 數據99 賦值給any
any = 99
fmt.Printf("any type = %T, value = %v\n",any,any)
// 聲明一個int型變量i
var i int
// 那麼是不是可以將any直接賦值給i呢?
// 實際是不行的,因爲any本質上是interface{}類型,而不是int型
// i = any
// 執行之後編譯器會拋出
// cannot use any (type interface {}) as type int in assignment: need type assertion
// 所以我們需要用到類型斷言,將interface{}類型轉換成int類型賦值給i
i = any.(int)
fmt.Printf("i type = %T, value = %v\n",i,i)
any = "golang"
fmt.Printf("any type = %T, value = %v\n",any,any)
any = true
fmt.Printf("any type = %T, value = %v\n",any,any)
}
看一個使用interface{} 的例子
package main
import "fmt"
type Dict struct {
// data 作爲map類型可以接受任意類型的鍵和值
data map[interface{}]interface{}
}
// 增加數據
func (d *Dict) addValue(key, value interface{}) {
d.data[key] = value
}
// 獲取數據
func (d *Dict) GetValue(i interface{}) interface{} {
return d.data[i]
}
// 清空
func (d *Dict) DelAll() {
d.data = make(map[interface{}]interface{})
}
func (d *Dict) Visit(callback func(k,v interface{}) bool ) {
if callback == nil{
return
}
for key,value := range d.data{
if ! callback(key,value){
return
}
}
}
// 初始化
func NewDict() *Dict {
d := new(Dict)
d.DelAll()
return d
}
func main() {
d := NewDict()
d.addValue("name", "tom")
d.addValue("male", true)
name := d.GetValue("name")
fmt.Println(name)
// 訪問
d.Visit(func(k, v interface{}) bool {
fmt.Println(k,v)
return true
})
}
go run main.go
tom
name tom
male true
4.7 如何實現多態
我們一直說在Go語言中多態是通過接口實現的,可以按照統一的接口,調用不同的實現,此時
接口變量
就呈現不同的形態
實現多態方法1:
把接口當做
參數
在方法或者函數中傳遞,根據傳遞進來的實際接口的不同實現,體現同一個接口不同的實現
package main
import (
"fmt"
)
// 定義一個接口USB,有兩個方法
type USB interface {
start()
stop()
}
// 定義Phone的類型的結構體
type Phone struct {
name string
}
// Phoen類型實現接口USB
func (p Phone) start() {
fmt.Println(p.name, "start")
}
func (p Phone) stop() {
fmt.Println(p.name, "stop")
}
// 定義Pad類型的結構體
type Pad struct {
name string
}
// Pad實現接口USB
func (p Pad) start() {
fmt.Println(p.name, "start")
}
func (p Pad) stop() {
fmt.Println(p.name, "stop")
}
// 定義machine類型的結構體
type Machine struct{
}
// 給Machine綁定方法Work
// 傳入的參數是USB類型,凡是實現了USB的變量都可以是參數
func (m Machine) Work(u USB){
u.start()
u.stop()
}
func main() {
var iphon Phone = Phone{"iphone"}
var ipad Pad = Pad{"ipad"}
// 實現接口的類型變量,可以賦值給接口變量
// 被賦值的接口變量,可以像類型變量一樣調用接口方法
// 接口方法被具體實現的不一樣,所有調用相同的方法,結果也是不一樣的
var usb USB
// 將實例iPhone賦值給接口變量usb
usb = iphon
usb.start()
// 將實例iPad賦值給接口變量usb
usb = ipad
usb.start()
m := Machine{}
// iPhone Iphone 實現USB接口 所以iPhone可以是變量
// 因爲USB接口的具體實現的不同,故同樣的USB接口呈現了多態
m.Work(iphon)
m.Work(ipad)
}
go run main.go
iphone start
ipad start
iphone start
iphone stop
ipad start
ipad stop
實現多態方法1:
把接口當做數據類型,那麼實現該接口的結構體(或者其他類型) 都是符合該接口類型
package main
import (
"fmt"
)
// 定義一個接口USB,有兩個方法
type USB interface {
start()
stop()
}
// 定義Phone的類型的結構體
type Phone struct {
name string
}
// Phoen類型實現接口USB
func (p Phone) start() {
fmt.Println(p.name, "start")
}
func (p Phone) stop() {
fmt.Println(p.name, "stop")
}
// 定義Pad類型的結構體
type Pad struct {
name string
}
// Pad實現接口USB
func (p Pad) start() {
fmt.Println(p.name, "start")
}
func (p Pad) stop() {
fmt.Println(p.name, "stop")
}
func main() {
// 定義一個map變量iArr它的鍵是string類型,值是USB接口類型
// 那麼只有實現了USB接口的數據才能符合
var iArr map[string]USB
iArr = make(map[string]USB)
// Phone結構體類型 實現了USB ,所以可以將Phone類型的結構體可以當做值賦給變量IArr
iArr["iPhone X"] = Phone{"iPhonex"}
// ipad結構體類型 實現了USB ,所以可以將ipad類型的結構體可以當做值賦給變量IArr
iArr["IPad2018"] = Pad{"Ipad2018款"}
// 調用方法
iArr["IPad2018"].start()
iArr["IPad2018"].stop()
iArr["P30"] = Phone{"華爲P30"}
iArr["Mate20"] = Phone{"Mate 20 X"}
iArr["P30"].stop()
iArr["P30"].start()
}
go run main.go
Ipad2018款 start
Ipad2018款 stop
華爲P30 stop
華爲P30 start