Go語言基礎(三)—— 面向對象編程

前言:
本專題用於記錄本身(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是一種面向對象的語言嗎?」併發

答案是:是也不是。編程語言

由於咱們都知道,面向對象的三大特性是:性能

  1. 封裝
    隱藏對象的屬性和實現細節,僅對外提供公共訪問方式,將變化隔離,便於使用,提升複用性和安全性。
  2. 繼承
    提升代碼複用性;繼承是多態的前提。
  3. 多態
    父類或接口定義的引用變量能夠指向子類或具體實現類的實例對象。提升了程序的拓展性。

然而,Go語言並不支持繼承。提倡使用組合(has-a)而不是繼承(is-a)。
具體內容可查看第一篇:《Go語言基礎(一)—— 簡介、環境配置、Hello World》所以,咱們說Go不是標準的「面向對象」語言。學習

那爲何又說也是「面向對象」語言呢?測試

雖然Go語言中沒法繼承,但它依然容許面向對象的編碼風格。
Go中提供了接口(協議)的概念,提供了一種面向的新思路,且在某些方面更通用,更高效。 所以,對比C++/Java等語言,Go變的更加輕量級。ui


1、結構體 / 行爲(方法)的定義

1.結構體的定義

type Employee struct {
	Id   int
	Name string
	Age  int
}
複製代碼

2.結構體的三種初始化方式:

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)
}
複製代碼

3.行爲(方法)的定義:

  • 拷貝對象的方法定義:
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())
}
複製代碼

打印結果:


2、接口(協議)

與其餘編程語言的區別:

非侵入性:實現不依賴與接口的定義。
所以,接口的定義能夠包含在接口的使用者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就是自定義類型。


3、擴展(複用)

在本篇的開始,已經提到過 —— 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確實不支持多繼承。


4、空接口 & 斷言

空接口:表示各類類型。 斷言:將空接口轉換成指定類型。

  • 空接口:
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接口?

主要有如下幾點:

  1. 使用小接口定義,甚至有的接口能夠只定義一個方法。(這樣,業務方在接入的時候,能夠根據本身的需求,只實現個別接口)

  2. 能夠採用接口套接口的形式,大接口由多個小接口組成。

  3. 業務方在實現功能時,只依賴於最小的接口。

舉個例子:

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語言從入門到實戰》 祝你們學有所成,工做順利。謝謝!

相關文章
相關標籤/搜索