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