前言:
本專題用於記錄本身(647)在Go語言方向的學習和積累。 系列內容比較偏基礎,推薦給想要入門Go語言開發者們閱讀。
目錄以下:
Go語言基礎(一)—— 簡介、環境配置、HelloWorld
Go語言基礎(二)—— 基本經常使用語法
Go語言基礎(三)—— 面向對象編程
Go語言基礎(四)—— 優質的容錯處理
Go語言基礎(五)—— 併發編程
Go語言基礎(六)—— 測試、反射、Unsafe
Go語言基礎(七)—— 架構 & 常見任務
Go語言基礎(八)—— 性能調優編程
本篇將介紹以下內容:
1.Go是面向對象的語言麼?
2.結構體與行爲(方法)的定義
3.接口(協議)安全
咱們先來看個引子:「Is Go an object-oriented language?」架構
在Go的官方論壇裏有世界各地的開發者討論,
問:「Go是一種面向對象的語言嗎?」併發
答案是:是也不是。編程語言
由於咱們都知道,面向對象的三大特性是:性能
然而,Go語言並不支持繼承。提倡使用組合(has-a)而不是繼承(is-a)。
具體內容可查看第一篇:《Go語言基礎(一)—— 簡介、環境配置、Hello World》 。 所以,咱們說Go不是標準的「面向對象」語言。學習
那爲何又說也是「面向對象」語言呢?測試
雖然Go語言中沒法繼承,但它依然容許面向對象的編碼風格。
Go中提供了接口(協議)的概念,提供了一種面向的新思路,且在某些方面更通用,更高效。 所以,對比C++/Java等語言,Go變的更加輕量級。ui
type Employee struct {
Id int
Name string
Age int
}
複製代碼
func TestCreateEmployeeObj(t *testing.T) {
/* 第一種構造方式,返回對象 */
e1 := Employee{1, "647", 23}
/* 第二種構造方式,返回對象 */
e2 := Employee{Id: 2, Name: "647", Age: 23}
/* 第三種構造方式,返回指針 */
e3 := new(Employee) // 返回指針
e3.Id = 3
e3.Age = 23
e3.Name = "647"
t.Log("e1 = ", e1)
t.Log("e2 = ", e2)
t.Log("e3 = ", *e3)
t.Logf("e1 is %T", e1) // %T打印類型
t.Logf("e2 is %T", e2)
t.Logf("e3 is %T", *e3)
}
複製代碼
func (e Employee) String1() string {
fmt.Printf("String1's e address is %x\n", unsafe.Pointer(&e.Name))
return fmt.Sprintf("Id:%d, Name:%s, Age:%d", e.Id, e.Name, e.Age)
}
複製代碼
func (e *Employee) String2() string {
fmt.Printf("String2's e address is %x\n", unsafe.Pointer(&e.Name))
return fmt.Sprintf("Id:%d, Name:%s, Age:%d", e.Id, e.Name, e.Age)
}
複製代碼
這兩種有什麼區別呢?編碼
其實從返回的結果來看是一致的,但「非拷貝行爲(方法)的定義」會複用原來的對象,而拷貝行爲(方法)的定義會複製出一個對象出來。(經過打印對象的內存地址能夠發現。)
demo:
func TestStructOperations(t *testing.T) {
e := Employee{1, "647", 23}
fmt.Printf("Origin's e address is %x\n", unsafe.Pointer(&e.Name))
t.Log("拷貝對象:", e.String1())
t.Log("非拷貝對象:", e.String2())
}
複製代碼
打印結果:
與其餘編程語言的區別:
非侵入性:實現不依賴與接口的定義。
所以,接口的定義能夠包含在接口的使用者package內。
舉個例子:
實例代碼:
// 接口(協議)的定義
type Programmer interface {
WriteHelloWorld() string
}
// 接口(協議)的類型
type GoProgrammer struct {
}
// 接口(協議)的實現
func (_ *GoProgrammer) WriteHelloWorld() string {
return "fmt.Println(\"Hello World\")"
}
// 測試demo:
func TestClient(t *testing.T) {
var coder = new(GoProgrammer)
t.Log(coder.WriteHelloWorld())
}
複製代碼
咱們能夠把經常使用的一些類型作一些簡化自定義,抽出來,便於代碼的簡潔和可讀。
例如,咱們想監測方法的耗時,用自定義類型簡化就變成了這樣:
type IntConv func(op int) int // 自定義類型 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
}
}
func slowFunc(op int) int {
time.Sleep(time.Second * 1)
return op
}
func TestFunc(t *testing.T) {
t.Log(timeSpent(slowFunc)(1))
}
複製代碼
其中:type IntConv func(op int) int
就是自定義類型。
在本篇的開始,已經提到過 —— Go自己是不支持繼承的。
所以,咱們只能經過組合(has-a)的方式,來達到相似的效果。
咱們舉一個Pet與Dog的例子,直接上代碼:
// Pet結構體
type Pet struct {
id int
name string
variety string
}
func (p *Pet) Speak() {
fmt.Print("Pet")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" speak to", host)
}
// Dog結構體
type Dog struct {
Pet // 組合的方式(至關於擁有了全部Pet的方法與屬性)
}
func (d *Dog) Speak() {
fmt.Println("wang wang wang.") // 但並不支持重載
}
// 測試結果發現,依然是Pet,說明不支持重載(由於不是繼承)
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("647")
}
複製代碼
可是咱們打印一下結果:
發現並非 Dog speak to 647?
爲何呢?由於Go自己不支持繼承,因此也不支持方法重載。 所以,咱們在調用dog.SpeakTo
的時候依然調用的是Pet的方法。 因而可知,Go確實不支持多繼承。
空接口:表示各類類型。 斷言:將空接口轉換成指定類型。
func DoSomething(p interface{}) {...}
複製代碼
v, ok := p.(int) // ok = true時,表明轉換成功。
複製代碼
舉個例子,根據數據類型,打印不一樣的數據:
func DoSomething(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
default:
fmt.Println("Unknown Type.")
}
}
func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10)
DoSomething("10")
}
複製代碼
那麼問題來了,如何設計一個好的Go接口?
主要有如下幾點:
使用小接口定義,甚至有的接口能夠只定義一個方法。(這樣,業務方在接入的時候,能夠根據本身的需求,只實現個別接口)
能夠採用接口套接口的形式,大接口由多個小接口組成。
業務方在實現功能時,只依賴於最小的接口。
舉個例子:
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
}
func DoSomething(reader Reader) error {
// ...
}
複製代碼
PS:另附上,分享連接:《Go語言從入門到實戰》 祝你們學有所成,工做順利。謝謝!