關於Go是不是面嚮對象語言其實有很多爭論,關於給出的解釋是:Yes and no.
封裝數據和行爲
結構體定義
type Employee struct { Id string Name string Age int }
實例創建及初始化
e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) //注意這裏返回的引用/指針, 相當於 e:= &Employee{} e2.Id = "2" //與其他主要編程語言的差異:通過實例的指針訪問成員不需要使用-> e2.Age = 22 e2.Name = "Rose"
行爲(方法)的定義
//第一種定義方式在實例對應方法被調用時,實例的成員會進行值複製 func (e Employee) String() string { return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } //第二種通常情況下爲了避免內存拷貝我們使用第二種定義方式 func (e *Employee) String() string { return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age) }
Duck Type式接口實現
接口定義
type Programmer interface { WriteHelloWorld() Code }
接口實現
type GoProgrammer struct { } func (p *GoProgrammer) WriteHelloWorld() Code { return "fmt.Println(\"Hello World!\")" }
Go接口
與其他主要編程語言的差異
- 接口爲非入侵性的,實現不依賴於接口的定義
- 所以接口的定義可以包含在接口使用者包內
接口變量
var prog Coder = &GoProgrammer{} // 以上prog是接口Coder的一個變量
當prog被初始化之後它有兩部分
prog
type GoProgrammer stuct { //類型 類型 --> } 數據 --> &GoProgrammer{} //具體的實現
自定義類型
舉例:
1. type IntConvertionFn func(n int) int
2. type MyPoint int
Go語言中接口的實現是不依賴於接口的定義的,是採用DockType的方式
接口
以上是筆者在進行Go語言面向對象方面知識所記錄的筆記,可能會有一些凌亂。如果你和我的技術棧類似,可能會對Go中的接口定義以及實現,Duck Type等部分內容感到新奇。可以看看接口這部分的代碼:
package interface_test import ( "testing" ) type Programmer interface{ WriteHelloWorld() string } type GoProgrammer struct{ } func (g *GoProgrammer) WriteHelloWorld() string{ return "fmt.Println(\"Hello World!\")" } /*注意這裏,其實p定義的是接口Programmer,而p = new(GoProgrammer) 是將接口Programmer的具體實現'GoProgrammer'作爲p的實例 這裏沒有使用傳統的【接口定義,接口實現繼承自接口定義,具體使用的地方利用接口定義通過容器或別的方式獲取接口實現】的傳統方法 而是使用了DuckType.所以DuckType就是指,這個鳥雖然我不知道是什麼,但是看起來腳上有蹼,扁嘴,像是鴨子,那麼將當它是鴨子。(2333,這是老師的原話) 對應上面就是接口GoProgrammer所對應的方法‘WriteHelloWorld’與Programmer中定義的方法看起來是一樣的,那麼我們就當GoProgrammer是Programmer的具體實現 */ func TestClinet(t *testing.T){ var p Programmer p = new(GoProgrammer) t.Log(p.WriteHelloWorld()) }
如上述代碼,如果是以"C#"之類的語言來看,其實我們在TestClient中是定義了一個Programmer接口的變量p,而p的具體實現則是 “類” GoProgrammer 。但我們觀察 “類” GoProgrammer,其實並不像C#中那樣要繼承自Programmer接口,只是這個方法定義的相同。這就是所謂的“Duck Type”.
自定義類型
而關於自定義類型,其實是可以自定義出一些“複雜”的類型,並利用這寫類型來簡化一些功能的實現.
package customer_type import ( "testing" "fmt" "time" ) type IntConv func(op int) int //通過自定義類型,讓程序有更好的可讀性,這裏自定義了IntCov類型,這個類型是入參爲一個int,返回一個int的函數 func timeSpent(inner func(op int) int) IntConv { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFun(op int) int{ time.Sleep(time.Second *1) return op } func TestFn(t *testing.T) { // 計算函數運行時間 tsSF := timeSpent(slowFun) t.Log(tsSF(10)) }
如上述代碼,我們的自定義類型IntConv其實是一個參數爲int,返回值爲int的函數。而timeSpent方法的返回值是IntConv類型,作用是計算某個函數的運行時間。如果你仔細觀察,其實會發現timeSpent不只返回值,參數其實也是IntConv類型,因此這個方法改寫成如下是完全OK的。
func timeSpent(inner IntConv) IntConv { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } }
而關於行爲方法的定義,其實沒什麼好說的,只是要注意兩種方式的不同,爲了避免內存複製,儘量使用 " Employee"這種方式.
package encapsulation import ( "testing" "fmt" "unsafe" ) type Employee struct { Id string Name string Age int } func TestCreateEmployeeObj(t *testing.T){ e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) //注意這裏返回的引用/指針, 相當於 e:= &Employee{} e2.Id = "2" //與其他主要編程語言的差異:通過實例的指針訪問成員不需要使用-> e2.Age = 22 e2.Name = "Rose" t.Log(e) t.Log(e1) t.Log(e1.Id) t.Log(e2) //%T 代表輸出類型 t.Logf("e is %T", e) //輸出 e is encapsulation.Employee t.Logf("e2 is %T", e2) //輸出 e2 is *encapsulation.Employee } //第一種定義方式在市裏對應方法被調用時,實例的成員會進行值複製 func (e Employee) String() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } //第二種通常情況下爲了避免內存拷貝我們使用第二種定義方式 func (e *Employee) String2() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age) } func TestStructOperations(t *testing.T) { e := Employee{"0", "Bob", 20} t.Log(e.String()) fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) /* 輸出爲: Address is c000064520 TestStructOperations: encap_test.go:45: ID:0-Name:Bob-Age:20 Address is c0000644f0 */ t.Log(e.String2()) fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) /* 輸出爲: Address is c0000644f0 TestStructOperations: encap_test.go:48: ID:0/Name:Bob/Age:20 Address is c0000644f0 */ //結論,第一種方式(func (e Employee) String() string)會有更大的內存開銷,因爲是把結構的數據copy了一份,而第二種方式(func (e *Employee) String2() string)引用了相同的地址。 }