• Go 其五 到底是不是面向对象语言 -- 封装数据和行为, 接口, 自定义类型


      关于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接口
      与其他主要编程语言的差异

    1. 接口为非入侵性的,实现不依赖于接口的定义
    2. 所以接口的定义可以包含在接口使用者包内


      接口变量

    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
    ", 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
    ", 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
    ", 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
    ", 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)引用了相同的地址。
    }
    

      

  • 相关阅读:
    zoj1151 zoj1295 Word Reversal 字符串的简单处理
    zoj 1539 Lot 简单DP 记忆化
    ZOJ 2042 Divisibility (DP)
    zoj 1889 ones 数学
    Kubernetes Ingress 日志分析与监控的最佳实践
    如何使用Data Lake Analytics创建分区表
    如何在Data Lake Analytics中使用临时表
    阿里敏捷教练:多团队开发一个产品的组织设计和思考
    阿里工程师开发了一款免费工具,提升Kubernetes应用开发效率
    触手可得的云原生 | 阿里云中间件发布多项新功能​
  • 原文地址:https://www.cnblogs.com/dogtwo0214/p/13289687.html
Copyright © 2020-2023  润新知