Go面向对象编程

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object�

oriented style of programming, there is no type hierarchy. The concept

of “interface” in Go provides a different approach that we believe is

easy to use and in some ways more general.

Also, the lack of a type hierarchy makes “objects” in Go feel much more

lightweight than in languages such as C++ or Java.

行为的定义和实现

结构体定义:

type Employee struct {
    Id   string
    Name string
    Age  int
}

实例创建及初始化:

func TestCreateEmployee(t *testing.T) {
    e := Employee{"0", "Bob", 20}         // 分别把值放进去
    e1 := Employee{Name: "Mike", Age: 30} // 指定某个field的值
    e2 := new(Employee)                   // new关键字 去创建指向实例的指针 这里返回的引用/指针 相当于 e:=Employee{}
    e2.Id = "2"                           // 通过 example.filed 去赋值
    e2.Name = "Rose"
    e2.Age = 22
    t.Log(e)
    t.Log(e1)
    t.Log(e1.Id)
    t.Log(e2)
    t.Logf("e is %T", e)
    t.Logf("e2 is %T", e2)
    /** 运行结果:
    === RUN   TestCreateEmployee
        TestCreateEmployee: encap_test.go:18: {0 Bob 20}
        TestCreateEmployee: encap_test.go:19: { Mike 30}
        TestCreateEmployee: encap_test.go:20:
        TestCreateEmployee: encap_test.go:21: &{2 Rose 22}
        TestCreateEmployee: encap_test.go:22: e is test.Employee
        TestCreateEmployee: encap_test.go:23: e2 is *test.Employee
    --- PASS: TestCreateEmployee (0.00s)
     */
}

行为定义:

// 第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
func (e Employee) String() string {
    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())
    /** 运行结果:
    === RUN   TestStructOperations
        TestStructOperations: encap_test.go:46: ID:0-Name:Bob-Age:20
    --- PASS: TestStructOperations (0.00s)
     */
}
// 通常情况下为了避免内存拷贝我们使用第二种定义方式
func (e *Employee) String() string {
    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())
    /** 运行结果:
    === RUN   TestStructOperations
        TestStructOperations: encap_test.go:51: ID:0/Name:Bob/Age:20
    --- PASS: TestStructOperations (0.00s)
    */
}

在Go语言中不管通过指针访问还是通过实例访问,都是一样的

那么这两种定义没有区别吗??

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 TestStructOperations(t *testing.T) {
    e := Employee{"0", "Bob", 20}
    fmt.Printf("Address is %x \n", unsafe.Pointer(&e.Name))
    t.Log(e.String())
    /** 运行结果:
    === RUN   TestStructOperations
    Address is c000060370
    Address is c000060370
        TestStructOperations: encap_test.go:54: ID:0/Name:Bob/Age:20
    --- PASS: TestStructOperations (0.00s)
    */
}

可以发现两个地址一致

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 TestStructOperations(t *testing.T) {
    e := Employee{"0", "Bob", 20}
    fmt.Printf("Address is %x \n", unsafe.Pointer(&e.Name))
    t.Log(e.String())
    /** 运行结果:
    === RUN   TestStructOperations
    Address is c000092370
    Address is c0000923a0
        TestStructOperations: encap_test.go:55: ID:0-Name:Bob-Age:20
    --- PASS: TestStructOperations (0.00s)
    */
}

这时候两个地址不一致,说明结构体的数据被复制了,会造成开销

Go语言的相关接口

Java的接口与依赖:

image

Go的 Duck Type 式接口实现:

  • 接口为非入侵性,实现不依赖与接口定义
  • 所以接口的定义可以包含在接口使用者包内
image
type Programmer interface {
    WriteHelloWorld() string
}

type GoProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() string {
    return "Hello World"
}

func TestClient(t *testing.T) {
    var p Programmer
    p = new(GoProgrammer)
    t.Log(p.WriteHelloWorld())
    /** 运行结果:
    === RUN   TestClient
        TestClient: interface_test.go:19: Hello World
    --- PASS: TestClient (0.00s)
     */
}

接口变量:

image

自定义类型:

  • type IntConvertionFn func(n int) int
  • type MyPoint int

扩展和复用

复合:

  • Go不支持继承,可以通过复合的方式来复用

匿名类型嵌入:

它不是继承,如果我们把“内部 struct”看作父类,把“外部 struct” 看作子类,会发现如下问题:

  • 不支持子类替换
  • 子类并不是真正继承了父类的方法
    • 父类定义的方法无法访问子类的数据和方法
type Pet struct {
}

func (p *Pet) Speak() {
    fmt.Print("...")
}

func (p *Pet) SpeakTo(string string) {
    p.Speak()
    fmt.Println(" ", string)
}

type Dog struct {
    p *Pet
}

func (d *Dog) Speak() {
    fmt.Print("Wang!")
}

func (d *Dog) SpeakTo(string string) {
    d.p.SpeakTo(string)
}

func TestDog(t *testing.T) {
    dog := new(Dog)
    dog.SpeakTo("Gao") // 没有打印 Wang! 需要改动 dog中SpeakTo方法
    /** 运行结果:
    === RUN   TestDog
    ...  Gao
    --- PASS: TestDog (0.00s)
     */
}

多态与空接口

多态:

type Code string // 自定义类型

type Programmer interface {
    WriteHelloWorld() Code
}

type GoProgrammer struct {
}

type PhpProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() Code {
    return "fmt.Println(\"Hello World\")"
}

func (p *PhpProgrammer) WriteHelloWorld() Code {
    return "echo \"Hello World\""
}

func writeFirstProgram(p Programmer) {
    fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}

func TestPolymorphism(t *testing.T) {
    goProg := new(GoProgrammer)
    phpProg := new(PhpProgrammer)
    writeFirstProgram(goProg)
    writeFirstProgram(phpProg)
    /** 运行结果
    === RUN   TestPolymorphism
    *test.GoProgrammer fmt.Println("Hello World")
    *test.PhpProgrammer echo "Hello World"
    --- PASS: TestPolymorphism (0.00s)
     */
}

空接口与断言:

  • 空接口可以表示任何类型

  • 通过断言来将空接口转换为制定类型

    v, ok := p.(int) // ok=true 时为转换成功

func DoSomething(p interface{}) {
    // 如果传入的参数能被断言成一个整型
    if i, ok := p.(int); ok {
        fmt.Println("Integer", i)
        return
    }

    // 如果传入的参数能被断言成一个字符型
    if s, ok := p.(string); ok {
        fmt.Println("String", s)
        return
    }

    fmt.Println("Unknow Type")

    // 也可以通过switch来判断
    /*switch v := p.(type) {
    case int:
        fmt.Println("Integer", v)
    case string:
        fmt.Println("String", v)
    default:
        fmt.Println("Unknow Type")
    }*/
}

func TestEmptyInterfaceAssertion(t *testing.T) {
    DoSomething(10)
    DoSomething("gaobinzhan")
    /** 运行结果
    === RUN   TestEmptyInterfaceAssertion
    Integer 10
    String gaobinzhan
    --- PASS: TestEmptyInterfaceAssertion (0.00s)
    */
}

Go接口最佳实践:

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270