【OOP】go語言學習筆記(第3章)—面向對象編程

一個典型的類型系統包括:php

  • 基礎類型: byte, int, bool, float等
  • 複合類型:數組,結構體,指針
  • 能夠指向任意對象的類型(Any類型)
  • 值語義和引用語義
  • 面向對象,即全部具有面向對象特徵的類型
  • 接口

在GO語言中能夠爲任意類型(包括內置類型)添加相應的方法html

go語言學習筆記(第3章)—面向對象編程
上面的例子中咱們定義了一個新類型 Integer ,Integer和int沒有本質區別,只是爲內置的int增長了一個方法Less(),可讓整型像一個普通的類同樣使用了。git

在GO語言中沒有隱藏的this指針github

1) 方法施加的目標顯示傳遞,沒有被隱藏起來golang

2) 方法施加的目標不須要非得是指針,也不用非得叫thisweb

GO語言和C語言同樣,類型都是基於值傳遞的,要想改變變量的值,只能在函數中傳遞指針。面試

1. 類型系統編程

1.1.  值語義和引用語義數組

值語義和引用語義的差異在於賦值app

b = a

b.Modify()

若是b的修改不會影響a的值,那麼此類型屬於值類型。若是會影響a的值,那麼此類型是引用類型。

GO語言中的大多數類型都屬於值語義,包括:

基本類型: byte, int, bool, float32, float64和string等

複合類型: array, struct, pointer等

GO語言中的類型的值語義表現的很是完全。

1.2. 結構體

GO語言放棄了包括繼承在內的大量面向對象特性,只保留了組合這個最基礎的特性。

組合不能算面向對象的特性,由於在C語言這樣的過程式編程語言中,也有結構體,也有組合。組合只是形式複合類型的基礎。

GO語言中結構體的使用方式與C語言並無什麼明顯的不一樣。

2. 初始化

在GO語言中,未進行顯式初始化的變量都會被初始化爲該類型的零值,例如bool類型的零值爲false, int類型的零值爲0, string類型的零值爲空字符串。

結構體有多種初始方法,以下:

go語言學習筆記(第3章)—面向對象編程
3. 匿名組合

確切的說,GO語言也提供了繼承,可是採用了組合的文法,因此咱們將其稱爲匿名組合

4. 可見性

GO語言對關鍵字的增長很是吝嗇,沒有private, protected, public這樣的關鍵字。要使某個符號對其餘包可見,須要將該符號定義爲以大寫字母開頭。

5. 接口

GO語言的接口並非其餘語言中所提供的接口概念。JAVA如今的接口是侵入式接口, GO的接口時非侵入式的。

在GO語言中,一個類只要實現了接口要求的全部函數,咱們就說這個類實現了該接口。

在GO語言中,接口賦值在GO語言中分爲以下兩種狀況:

1. 將對象實例賦值給接口

2. 將一個接口賦值給另外一個接口

//struct
//Date:2014-4-1 09:57:37
package main
import (
	"fmt"
	"strings"
)

func StructTest01Base() {
	//structTest0101()
	//structTest0102()
	structTest0103()

}

//定義一個struct
type Student struct {
	id      int
	name    string
	address string
	age     int
}

func structTest0101() {
	//使用new建立一個Student對象,結果爲指針類型
	//var s *Student = new(Student)
	s := &Student{}
	//var s *Student = new(Student)
	//var s *Student = new(Student)
	s.id = 101
	s.name = "Mikle"
	s.address = "紅旗南路"
	s.age = 18

	fmt.Printf("id:%d\n", s.id)
	fmt.Printf("name:%s\n", s.name)
	fmt.Printf("address:%s\n", s.address)
	fmt.Printf("age:%d\n", s.age)
	fmt.Println(s)
}


func main(){

	structTest0101()
}
//建立Student的其它方式
func structTest0102() {
	//使用&T{...}建立struct,結果爲指針類型
	var s1 *Student = &Student{102, "John", "Nanjing Road", 19}
	fmt.Println(s1)
	fmt.Println("modifyStudentByPointer...")
	modifyStudentByPointer(s1)
	fmt.Println(s1)

	//使用T{...}建立struct,結果爲value類型
	fmt.Println("-------------")
	var s2 Student = Student{103, "Smith", "Heping Road", 20}
	fmt.Println(s2)
	fmt.Println("modifyStudent...")
	modifyStudent(s2)
	fmt.Println(s2)
	//建立並初始化一個struct時,通常使用【上述】兩種方式

	//其它方式
	var s3 *Student = &Student{id: 104, name: "Lancy"}
	fmt.Printf("s3:%d,%s,%s,%d\n", s3.id, s3.name, s3.address, s3.age)
}

//struct對象屬於值類型,所以須要經過函數修改其原始值的時候必須使用指針
func modifyStudent(s Student) {
	s.name = s.name + "-modify"
}
func modifyStudentByPointer(s *Student) {
	s.name = s.name + "-modify"
}

type Person struct {
	firstName string
	lastName  string
}

//使用 *Person做爲參數的函數
func upPerson(p *Person) {
	p.firstName = strings.ToUpper(p.firstName)
	p.lastName = strings.ToUpper(p.lastName)
}

//調用上述方法的三種方式
func structTest0103() {
	//1- struct as a value type:
	var p1 Person
	p1.firstName = "Will"
	p1.lastName = "Smith"
	upPerson(&p1)
	fmt.Println(p1)

	//2—struct as a pointer:
	var p2 = new(Person)
	p2.firstName = "Will"
	p2.lastName = "Smith"
	(*p2).lastName = "Smith" //this is also valid
	upPerson(p2)
	fmt.Println(p2)

	//3—struct as a literal:
	var p3 = &Person{"Will", "Smith"}
	upPerson(p3)
	fmt.Println(p3)
}

轉載-golang面向對象分析

目錄

一. 抽象和封裝
二. 繼承(Composition)
1. has-a
2. is-a(Pseudo)----Embedding
三. Interface
尾聲
 
 
這篇文章寫的真心是好, 把Golang的OOP詮釋的很清楚.
本imi增長了, 本身的理解.
struct有內嵌(組合), 有重寫, 可是沒有重載; interface具備多態性.
 
說道面向對象(OOP)編程, 就不得不提到下面幾個概念:
- 抽象
- 封裝
- 繼承(重寫, 重載)
- 多態

其實有個問題Is Go An Object Oriented Language?, 隨便谷歌了一下, 你就發現討論這個的文章有不少:
1. reddit
2. google group

那麼問題來了

  1. Golang是OOP嗎?
  2. 使用Golang如何實現OOP?

我入門教程基本就是A Tour Of Go以及Go Web 編程. 因爲以前是寫C++, 可是說到Go面向對象編程, 老是感受怪怪的, 總感受缺乏點什麼. 我搜集了一些資料和例子, 加上個人一些理解, 整理出這樣一篇文章.

一. 抽象和封裝

抽象和封裝就放在一塊說了. 這個其實挺簡單. 看一個例子就好了.

type rect struct {
    width int
    height int
}
 
func (r *rect) area() int {
    return r.width * r.height
}
 
func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
}

完整代碼

要說明的幾個地方:
一、Golang中的struct和其餘語言的class是同樣的.

二、可見性. 這個遵循Go語法的大小寫的特性

三、上面例子中, 稱*rectreceiver. 關於receiver 能夠有兩種方式的寫法:

func (r *rect) area() int {
    return r.width * r.height
}
func (r rect) area() int {
    return r.width * r.height
}

這其中有什麼區別和聯繫呢? 關於詳細解釋請查看astaxie的解釋, 寫的很是清晰.

簡單來講, Receiver能夠是值傳遞, 仍是能夠是指針, 二者的差異在於, 指針做爲Receiver會對實例對象的內容發生操做,而普通類型做爲Receiver僅僅是以副本做爲操做對象,並不對原實例對象發生操做。

四、當Receiver*rect指針的時候, 使用的是r.width, 而不是(*r).width, 是因爲Go自動幫我轉了,兩種方式都是正確的.

五、任何類型均可以聲明成新的類型, 由於任何類型均可以有方法.

type Interger int
func (i Interger) Add(interger Interger) Interger {
    return i + interger
}

六、雖然Interger是從int聲明而來, 可是這樣用是錯誤的.

var i Interger = 1
var a int = b //cannot use i (type Interger) as type int in assignment

這是由於Go中沒有隱式轉換(寫C++的同窗都會特別討厭這個, 由於編譯器揹着咱們乾的事情太多了). Golang中類型之間的相互賦值都必須顯式聲明.

上面的例子改爲下面的方式就能夠了.

var i Interger = 1
var a int = int(b)

二. 繼承(Composition)

說道繼承,其實在Golang中是沒有繼承(Extend)這個概念. 由於Golang捨棄掉了像C++, Java的這種傳統的、類型驅動的子類。

Go Effictive says:
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to 「borrow」 pieces of an implementation by embedding types within a struct or interface.

換句話說, Golang中沒有繼承, 只有Composition.

Golang中的Compostion有兩種形式, 匿名組合(Pseudo is-a)非匿名組合(has-a)

注: 若是不瞭解OOP的is-ahas-a關係的話, 請自行google.

1. has-a

package main
 
import (
    "fmt"
)
 
type Human struct {
    name  string
    age   int
    phone string
}
 
type Student struct {
    h      Human //非匿名字段
    school string
}
 
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
 
func (s *Student) SayHi() {
    fmt.Printf("Hi student, I am %s you can call me on %s", s.h.name, s.h.phone)
}
 
func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    fmt.Println(mark.h.name, mark.h.age, mark.h.phone, mark.school)
    mark.h.SayHi()
    mark.SayHi()
 
}

 

Output

Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY
Hi student, I am Mark you can call me on 222-222-YYYY

完整代碼

這種組合方式, 其實對於瞭解傳統OOP的話, 很好理解, 就是把一個struct做爲另外一個struct的字段.

從上面例子能夠, Human徹底做爲Student的一個字段使用. 因此也就談不上繼承的相關問題了.咱們也不去重點討論.

2. is-a(Pseudo)----Embedding

type Human struct {
    name string
    age int
    phone string
}
 
type Student struct {
    Human //匿名字段
    school string
}
 
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
 
func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    fmt.Println(mark.name, mark.age, mark.phone, mark.school)
    mark.SayHi()
}

Output

Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY

完整代碼

這裏要說的有幾點:

一、字段
如今Student訪問Human的字符, 就能夠直接訪問了, 感受就是在訪問本身的屬性同樣. 這樣就實現了OOP的繼承.

fmt.Println("Student age:", mark.age) //輸出: Student age: 25

可是, 咱們也能夠間接訪問:

fmt.Println("Student age:", mark.Human.age) //輸出: Student age: 25

這有個問題, 若是在Student也有個字段name, 那麼當使用mark.name會以Studentname爲準.

fmt.Println("Student name:", mark.name) //輸出:Student Name: student name

完整代碼

二、方法
Student也繼承了HumanSayHi()方法

mark.SayHi() // 輸出: Hi, I am Mark you can call me on 222-222-YYYY

固然, 咱們也能夠重寫SayHi()方法:

type Human struct {
    name  string
    age   int
    phone string
}
 
type Student struct {
    Human  //匿名字段
    school string
    name   string
}
 
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
 
func (h *Student) SayHi() {
    fmt.Println("Student Sayhi")
}
 
func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT", "student name"}  
    mark.SayHi()
}

Output

Student Sayhi

完整代碼

三、爲何稱其爲Pseudo is-a呢?

由於匿名組合不提供多態的特性. 以下面的代碼:

package main
 
type A struct{
}
 
type B struct {
    A  //B is-a A
}
 
func save(A) {
    //do something
}
 
func main() {
    b := new(B)
    save(*b);  
}

Output

cannot use *b (type B) as type A in argument to save

完整代碼

還有一個面試題的例子(說明go的匿名組合只有重寫, 沒有重載)

type People struct{}
 
func (p *People) ShowA() {
    fmt.Println("showA")
    p.ShowB()
}
func (p *People) ShowB() {
    fmt.Println("showB")
}
 
type Teacher struct {
    People
}
 
func (t *Teacher) ShowB() {
    fmt.Println("teacher showB")
}
 
func main() {
    t := Teacher{}
    t.ShowA()
}

輸出結果是什麼呢?

Output

ShowA
ShowB

Effective Go Says:

There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one

也就是說, Teacher因爲組合了People, 因此Teacher也有了ShowA()方法, 可是在ShowA()方法裏執行到ShowB時, 這個時候的receiver*People而不是*Teacher, 主要緣由仍是由於embedding是一個Pseudo is-a, 沒有多態的功能.

四、 "多繼承"的問題(go沒有多重繼承, 必須顯示引用)

package main
 
import "fmt"
 
type School struct {
    address string
}
 
func (s *School) Address() {
    fmt.Println("School Address:", s.address)
}
 
type Home struct {
    address string
}
 
func (h *Home) Address() {
    fmt.Println("Home Address:", h.address)
}
 
type Student struct {
    School
    Home
    name string
}
 
func main() {
    mark := Student{School{"aaa"}, Home{"bbbb"}, "cccc"}
    fmt.Println(mark)
    mark.Address()
    fmt.Println(mark.address)
 
    mark.Home.Address()
    fmt.Println(mark.Home.address)
}

輸出結果:

30: ambiguous selector mark.Address
31: ambiguous selector mark.address

完整代碼

由此能夠看出, Golang中不論是方法仍是屬性都不存在相似C++那樣的多繼承的問題. 要訪問Embedding相關的屬性和方法, 須要在加那個相應的匿名字段, 如:

mark.Home.Address()

五、Embedding value和 Embedding pointer的區別

package main
 
import (
    "fmt"
)
 
type Person struct {
    name string
}
 
type Student struct {
    *Person
    age int
}
 
type Teacher struct {
    Person
    age int
}
 
func main()  {
    s := Student{&Person{"student"}, 10}
    t := Teacher{Person{"teacher"}, 40}
    fmt.Println(s, s.name)
    fmt.Println(t, t.name)
}

Output

{0x1040c108 10} student
{{teacher} 40} teacher

完整代碼

I. 二者對於結果來講, 沒有啥區別, 只是對傳參的時候有影響
II. Embedding value是比較常規的寫法
III. Embedding pointer比較有優點一點, 不須要關注指針是什麼時間被初始化的.

三. Interface

Golang中Composite不提供多態的功能, 那是否Golang不提供多態呢? 答案確定是否認. Golang依靠Interface實現多態的功能.

下面是我工程裏面一段代碼的簡化:

package main
 
import (
    "fmt"
)
 
type Check interface {
    CheckOss()
}
 
type CheckAudio struct {
    //something
}
 
func (c *CheckAudio) CheckOss() {
    fmt.Println("CheckAudio do CheckOss")
}
 
func main() {
    checkAudio := CheckAudio{}
 
    var i Check
 
    i = &checkAudio //想一下這裏爲啥須要&, 這一點是不少書裏面提到的, 
//即指針才具備指針的方法
//而反過來是不成立的, 非指針不會具備指針的方法
    i.CheckOss()
}

完整代碼

一、Interface 如何Composite ?

type Reader interface {
    Read(p []byte) (n int, err error)
}
 
type Writer interface {
    Write(p []byte) (n int, err error)
}
 
type ReadWriter interface {
    Reader
    Writer
}

其實很簡單, 就是把ReaderWriter嵌入到ReadWriter中, 這樣ReadWriter就擁有了ReaderWriter的方法.

尾聲

至此, 基本說完了Golang的面向對象. 有哪裏我理解的不對的地方, 請給我留言.

參考資料
1. Effective Go: Embedding
2. Go面試題
3. Is Go An Object Oriented Language?
4. go web編程
5. object-oriented-programming-in-go

0
相關文章
相關標籤/搜索