Go 語言面向對象保姆級教程!
哈嘍,大家好,我是 Go 大叔,專注分享 Go 語言知識,一起進入 Go 的大門。
大叔和身邊一羣大牛都無限看好 Go 語言,現在開始搞 Go 語言,過兩年大概就是第一批喫螃蟹的人。
面向對象基本概念
面向對象思想
-
面向對象 (Object Oriented,OO) 是軟件開發方法
-
面向對象是一種對現實世界抽象的理解,是計算機編程技術發展到一定階段後的產物
-
Object Oriented Programming-OOP ——面向對象編程
面向對象和麪向過程區別
-
面向對象是相對面向過程而言
-
面向對象和麪向過程都是一種思想
-
面向過程
-
強調的是功能行爲
-
關注的是解決問題需要哪些步驟
-
回想下前面我們完成一個需求的步驟:
-
首先搞清楚我們要做什麼
-
然後分析怎麼做
-
最後我用代碼體現
-
一步一步去實現,而具體的每一步都需要我們去實現和操作
-
在上面每一個具體步驟中我們都是參與者, 並且需要面對具體的每一個步驟和過程, 這就是面向過程最直接的體現
-
面向對象
-
將功能封裝進對象,強調具備了功能的對象
-
關注的是解決問題需要哪些對象
-
當需求單一, 或者簡單時, 我們一步一步去操作沒問題, 並且效率也挺高。可隨着需求的更改, 功能的增加, 發現需要面對每一個步驟非常麻煩, 這時就開始思索, 能不能把這些步驟和功能再進行封裝, 封裝時根據不同的功能,進行不同的封裝,功能類似的封裝在一起。這樣結構就清晰多了, 用的時候, 找到對應的類就可以了, 這就是面向對象思想
-
示例
-
買電腦
-
找班長
-
描述需求
-
班長把電腦買回來
-
瞭解電腦
-
瞭解自己的需求
-
對比參數
-
去電腦城
-
砍價,付錢
-
買回電腦
-
被坑
-
面向過程
-
面向對象
-
喫飯
-
去飯店
-
點菜
-
喫
-
去超市賣菜
-
摘菜
-
洗菜
-
切菜
-
炒菜
-
盛菜
-
喫
-
面向過程
-
面向對象
-
洗衣服
-
買電腦 / 喫飯 / 洗衣服
-
找個對象
-
脫衣服
-
打開洗衣機
-
丟進去
-
一鍵洗衣烘乾
-
脫衣服
-
放進盆裏
-
放洗衣液
-
加水
-
放衣服
-
搓一搓
-
清一清
-
擰一擰
-
曬起來
-
面向過程
-
面向對象
-
終極面向對象
-
現實生活中我們是如何應用面相對象思想的
-
包工頭
-
汽車壞了
-
面試
面向對象的特點
-
是一種符合人們思考習慣的思想
-
可以將複雜的事情簡單化
-
將程序員從執行者轉換成了指揮者
-
完成需求時:
-
先要去找具有所需的功能的對象來用
-
如果該對象不存在,那麼創建一個具有所需功能的對象
-
這樣簡化開發並提高複用
類與對象的關係
-
面向對象的核心就是對象, 那怎麼創建對象?
-
現實生活中可以根據模板創建對象, 編程語言也一樣, 也必須先有一個模板, 在這個模板中說清楚將來創建出來的對象有哪些
屬性
和行爲
-
Go 語言中的類相當於圖紙,用來描述一類事物。也就是說要想創建對象必須先有類
-
Go 語言利用類來創建對象,對象是類的具體存在, 因此面向對象解決問題應該是先考慮需要設計哪些類,再利用類創建多少個對象
如何設計一個類
-
生活中描述事物無非就是描述事物的
屬性
和行爲
。 -
如:人有身高,體重等屬性,有說話,打架等行爲。
事物名稱(類名):人(Person)
屬性:身高(height)、年齡(age)
行爲(功能):跑(run)、打架(fight)
-
Go 語言中用類來描述事物也是如此
-
屬性:對應類中的成員變量。
-
行爲:對應類中的成員方法。
-
定義類其實在定義類中的成員 (成員變量和成員方法)
-
擁有相同或者類似
屬性
(狀態特徵)和行爲
(能幹什麼事)的對象都可以抽像成爲一個類 -
如何分析一個類
-
一般名詞都是類 (名詞提煉法)
-
飛機發射兩顆炮彈摧毀了 8 輛裝甲車
飛機
炮彈
裝甲車
- 隔壁老王在公車上牽着一條叼着熱狗的草泥馬
老王
熱狗
草泥馬
如何定義一個類
-
類是用於描述事物的的屬性和行爲的, 而 Go 語言中的結構體正好可以用於描述事物的屬性和行爲
-
所以在 Go 語言中我們使用結構體來定義一個類型
type Person struct {
name string // 人的屬性
age int // 人的屬性
}
// 人的行爲
func (p Person)Say() {
fmt.Println("my name is", p.name, "my age is", p.age)
}
如何通過類創建一個對象
- 不過就是創建結構體的時候, 根據每個對象的特徵賦值不同的屬性罷了
// 3.創建一個結構體變量
p1 := Person{"lnj", 33}
per.say()
p2 := Person{"zs", 18}
per.Say()
不同包中變量、函數、方法、類型公私有問題
-
在 Go 語言中通過首字母大小寫來控制變量、函數、方法、類型的公私有
-
如果首字母小寫, 那麼代表私有, 僅在當前包中可以使用
-
如果首字母大寫, 那麼代表共有, 其它包中也可以使用
package demo
import "fmt"
var num1 int = 123 // 當前包可用
var Num1 int = 123 // 其它包也可用
type person struct { // 當前包可用
name string // 當前包可用
age int // 當前包可用
}
type Student struct { // 其它包也可用
Name string // 其它包也可用
Age int // 其它包也可用
}
func test1() { // 當前包可用
fmt.Println("test1")
}
func Test2() { // 其它包也可用
fmt.Println("Test2")
}
func (p person)say() { // 當前包可用
fmt.Println(p.name, p.age)
}
func (s Student)Say() { // 其它包也可用
fmt.Println(s.Name, s.Age)
}
面向對象三大特性
-
封裝性
-
封裝性就是隱藏實現細節,僅對外公開接口
-
-
類是數據與功能的封裝,數據就是成員變量,功能就是方法
-
爲什麼要封裝?
-
不封裝的缺點:當一個類把自己的成員變量暴露給外部的時候, 那麼該類就失去對該成員變量的管理權,別人可以任意的修改你的成員變量
-
封裝就是將數據隱藏起來, 只能用此類的方法纔可以讀取或者設置數據, 不可被外部任意修改是面向對象設計本質 (
將變化隔離
)。這樣降低了數據被誤用的可能 (提高安全性
和靈活性
)
package model
import "fmt"
type Person struct { // 其它包也可用
name string // 當前包可用
age int // 當前包可用
}
func (p *person)SetAge(age int) {
// 安全校驗
if age < 0 {
fmt.Println("年齡不能爲負數")
}
p.age = age
}
package main
import (
"fmt"
"main/model"
)
func main() {
// 報錯, 因爲name和age不是公開的
//p := model.Person{"lnj", 18}
// 方式一
//p := model.Person{}
//p.SetAge(18)
//fmt.Println(p)
// 方式二
//p := new(model.Person)
//p.SetAge(18)
//fmt.Println(p)
}
-
封裝原則
-
將不需要對外提供的內容都隱藏起來, 把屬性都隱藏, 提供公共的方法對其訪問
-
繼承性
-
Go 語言認爲雖然繼承能夠提升代碼的複用性, 但是會讓代碼腐爛, 並增加代碼的複雜度.
-
所以 go 語言堅持了〃組合優於繼承〃的原則, Go 語言中所謂的繼承其實是利用組合實現的 (匿名結構體屬性)
-
普通繼承 (組合)
package main
import "fmt"
type Person struct {
name string
age int
}
type Student struct {
Person // 學生繼承了人的特性
score int
}
type Teacher struct {
Person // 老師繼承了人的特性
Title string
}
func main() {
s := Student{Person{"lnj", 18}, 99}
//fmt.Println(s.Person.name)
fmt.Println(s.name) // 兩種方式都能訪問
//fmt.Println(s.Person.age)
fmt.Println(s.age) // 兩種方式都能訪問
fmt.Println(s.score)
}
- 繼承結構中出現重名情況, 採用就近原則
package main
import "fmt"
type Person struct {
name string // 屬性重名
age int
}
type Student struct {
Person
name string // 屬性重名
score int
}
func main() {
s := Student{Person{"zs", 18}, "ls", 99}
fmt.Println(s.Person.name) // zs
fmt.Println(s.name) // ls
//fmt.Println(s.Person.age)
fmt.Println(s.age) // 兩種方式都能訪問
fmt.Println(s.score)
}
- 多重繼承
package main
import "fmt"
type Object struct {
life int
}
type Person struct {
Object
name string
age int
}
type Student struct {
Person
score int
}
func main() {
s := Student{Person{Object{77}, "zs", 33}, 99}
//fmt.Println(s.Person.Object.life)
//fmt.Println(s.Person.life)
fmt.Println(s.life) // 三種方式都可以
//fmt.Println(s.Person.name)
fmt.Println(s.name) // 兩種方式都能訪問
//fmt.Println(s.Person.age)
fmt.Println(s.age) // 兩種方式都能訪問
fmt.Println(s.score)
}
package main
import "fmt"
type Object struct {
life int
}
type Person struct {
name string
age int
}
type Student struct {
Object
Person
score int
}
func main() {
s := Student{Object{77}, Person{"zs", 33}, 99}
//fmt.Println(s.Person.life)
fmt.Println(s.life) // 兩種方式都可以
//fmt.Println(s.Person.name)
fmt.Println(s.name) // 兩種方式都能訪問
//fmt.Println(s.Person.age)
fmt.Println(s.age) // 兩種方式都能訪問
fmt.Println(s.score)
-
方法繼承
-
在 Go 語言中子類不僅僅能夠繼承父類的屬性, 還能夠繼承父類的方法
package main
import "fmt"
type Person struct {
name string
age int
}
// 父類方法
func (p Person)say() {
fmt.Println("name is ", p.name, "age is ", p.age)
}
type Student struct {
Person
score float32
}
func main() {
stu := Student{Person{"zs", 18}, 59.9}
stu.say()
}
-
繼承中的方法重寫
-
如果子類有和父類同名的方法, 那麼我們稱之爲方法重寫
package main
import "fmt"
type Person struct {
name string
age int
}
// 父類方法
func (p Person)say() {
fmt.Println("name is ", p.name, "age is ", p.age)
}
type Student struct {
Person
score float32
}
// 子類方法
func (s Student)say() {
fmt.Println("name is ", s.name, "age is ", s.age, "score is ", s.score)
}
func main() {
stu := Student{Person{"zs", 18}, 59.9}
// 和屬性一樣, 訪問時採用就近原則
stu.say()
// 和屬性一樣, 方法同名時可以通過指定父類名稱的方式, 訪問父類方法
stu.Person.say()
}
-
注意點:
-
無論是屬性繼承還是方法繼承, 都只能子類訪問父類, 不能父類訪問子類
-
多態性
-
多態就是某一類事物的多種形態
貓: 貓-->動物
狗: 狗-->動物
男人 : 男人 -->人 -->高級動物
女人 : 女人 -->人 -->高級動物
- Go 語言中的多態是採用接口來實現的
package main
import "fmt"
// 1.定義接口
type Animal interface {
Eat()
}
type Dog struct {
name string
age int
}
// 2.實現接口方法
func (d Dog)Eat() {
fmt.Println(d.name, "正在喫東西")
}
type Cat struct {
name string
age int
}
// 2.實現接口方法
func (c Cat)Eat() {
fmt.Println(c.name, "正在喫東西")
}
// 3.對象特有方法
func (c Cat)Special() {
fmt.Println(c.name, "特有方法")
}
func main() {
// 1.利用接口類型保存實現了所有接口方法的對象
var a Animal
a = Dog{"旺財", 18}
// 2.利用接口類型調用對象中實現的方法
a.Eat()
a = Cat{"喵喵", 18}
a.Eat()
// 3.利用接口類型調用對象特有的方法
//a.Special() // 接口類型只能調用接口中聲明的方法, 不能調用對象特有方法
if cat, ok := a.(Cat); ok{
cat.Special() // 只有對象本身才能調用對象的特有方法
}
}
-
多態優點
-
多態的主要好處就是簡化了編程接口。它允許在類和類之間重用一些習慣性的命名, 而不用爲每一個新的方法命名一個新名字。這樣, 編程接口就是一些抽象的行爲的集合, 從而和實現接口的類的區分開來
-
多態也使得代碼可以分散在不同的對象中而不用試圖在一個方法中考慮到所有可能的對象。這樣使得您的代碼擴展性和複用性更好一些。當一個新的情景出現時, 您無須對現有的代碼進行改動, 而只需要增加一個新的類和新的同名方法
一個人走的太慢,一羣人才能走的更遠。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/__UUZFFSnNq_4uwUzIT2mA