Golang 复合数据类型:方法

传统的面向对象编程

在面向对象编程(OOP)中,类与对象是面向对象编程的两个主要方面。一个 类(Class) 能够创建一种新的 类型(Type) ,其中 对象(Object) 就是类的 实例(Instance) 。可以这样来类比:你可以拥有类型 int 的变量,也就是说存储整数的变量是 int 类的实例(对象)。

对象可以使用 属于 它的普通变量来存储数据。这种从属于对象或类的变量叫作 字段(Field) 。对象还可以使用 属于 类的函数来实现某些功能,这种函数叫作类的 方法(Method) 。这两个术语很重要,它有助于我们区分函数与变量,哪些是独立的,哪些又是属于类或对象的。总之,字段与方法通称类的 属性(Attribute)

方法

在 Golang 中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量;接收者可以是任意类型(指针、接口除外),包括结构体类型、函数类型、可以是 int ,boll,string或数组别名类型。

  • 接收者不能是一个接口类型,因为接口是一个抽象定义,方法却必须要具体实现
  • 接收者不能是一个指针类型,但可以是任何其他类型允许的指针

方法重载

类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的。 因为 方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法,但是如果基于接收器类型,是有重载的 。如下面的例子,具有同样名字的方法 Add 可以在 2 个或多个不同的接收器类型 *denseMatrix*sparseMatrix 上存在,比如在同一个包里这么做是允许的:

//a是接收器变量,*denseMatrix和*sparseMatrix是接收器类型,Add是方法,b Matrix是参数列表,Matrix是返回参数
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix

接收器的标准格式

  • recv 是接收器名称, receiver_type 是接收器类型( 接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母 ,例如 Socket 类型的接收器变量应该命名为 s )
  • methodName 是方法名, paramater_list 是参数列表, return_value_list 是返回值列表
func (recv receiver_type)methodName(paramater_list)(return_value_list) {...}

接收器的类型

在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器,大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。

  • 指针类型

    由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self;但 Go 并没有 this 和 self 这两个关键字,因此,也可以使用 this 和 self 作为接收者的实例化名字(this 和 self 跟一般的实例化名字没什么两样)。

    //定义TwoInts结构体
    type TwoInts struct {
        a int
        b int
    }
    func main(){
        two1 := new(TwoInts)//实例化,返回指针*TwoInts
        two1.a = 12
        two1.b = 10
        //调用AddThem方法
        fmt.Println(two1.AddThem())
        //调用AddToParam方法
        fmt.Println(two1.AddToParam(20))
    
        two2 := TwoInts{3,4} //实例化,返回结构体TwoInts{3,4}
        //调用AddThem方法
        fmt.Println(two2.AddThem())
    }
    //指针接收器的两个值相加方法
    func (tn *TwoInts)AddThem()int{
        return tn.a + tn.b
    }
    //指针接收器的三个值相加方法
    func (tn *TwoInts)AddToParam(param int)int{
        return tn.a + tn.b + param
    }
    
    /*
    22
    42
    7
    */

    代码说明:

    • 定义一个 TwoInts 结构,拥有两个整型的成员变量。使用名字 two1 实例化 TwoInts 结构体,返回指针类型 *TwoInts ,分别设置实例化成员变量 two1.atwo1,b 的值为 1210 ;使用名字 two2 实例化 TwoInts 结构体,设置成员变量返回结构体 TwoInts
    • 构造成员变量的方法 AddThem ,设置方法的接收器类型为指针 *TwoInts ,返回 tn.a + tn.b 的整型值;因此可以修改成员值,即便退出方法,也有效;构造另一个成员变量的方法 AddToParam ,设置方法的接收器类型为指针 *TwoInts ,返回 tn.a + tn.b + param 的整型值。
    • main 函数中,使用 fmt.Println 函数分别调用 AddThemAddToParam 方法,获取成员变量的值。
  • 非指针类型

    当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。

    // 定义点结构
    type Point struct {
        X int
        Y int
    }
    // 非指针接收器的加方法
    func (p Point) Add(other Point) Point {
        // 成员值与参数相加后返回新的结构
        return Point{p.X + other.X, p.Y + other.Y}
    }
    func main() {
        // 初始化点
        p1 := Point{3, 4}
        p2 := Point{2, 5}
        // 与另外一个点相加
        result := p1.Add(p2)
        // 输出结果
        fmt.Println(result.X,result.Y,result)
    }
    
    /*
    5 9 {5 9}
    */

    代码说明:

    • 定义一个 Point 点结构,拥有 XY 两个整型的成员变量
    • Point 结构定义一个非指针接收器的 Add 方法,传入和返回都是 Point 的结构,可以方便地实现多个点连续相加的效果,例如 P4 := P1.Add( P2 ).Add( P3 )
    • p1p2 实例化两个点
    • p1p2 两个点相加后返回结果并存储到 result
    • 打印结果 resultXY 相加的值

      由于例子中使用了非指针接收器, Add() 方法变得类似于只读的方法,Add() 方法内部不会对成员进行任何修改

### 函数和方法的区别

函数将变量作为参数:Function(recv);方法在变量上被调用:recv.Method1()

  • 当接收者是指针时,方法可以改变接收者的值和状态。(对于方法来说)
  • 当参数作为指针传递时,即通过引用调用时,函数也可以改变参数的状态。(对于函数来说)

### Golang设计模式之工厂方法

参考链接: Golang设计模式之工厂方法(掘金)

在面向对象编程中,可以通过构造子方法实现工厂模式,但 Golang 并不是面向对象编程语言,因此不能使用构造子方法来实现设计模式,而是相应地提供了其他方案。以结构体为例,通常会为结构体类型定义一个工厂,工厂的名字以 new 或者 New 开头。

//不强制构造函数,首字母大写
type File struct {
    fd int
    name string
}

//构造工厂方法
func NewFile(fd int,name string) *File{
    if fd < 0 {
        return nil
    }
    return &File{fd,name}
}

func main() {
    //调用工厂方法NewFile
    f := NewFile(10,"./test.yxy")
    fmt.Println(f)
    //计算结构体占用多少内存
    size := unsafe.Sizeof(File{})
    fmt.Println(size)
}

/*
&{10 ./test.yxy}
24
*/

代码说明:

  • 以首字母为大写,不强制使用构造函数,创建结构体 File
  • 为这个结构体构造一个工厂方法 NewFile ,并返回指向结构体的指针 *File
  • main 函数调用该工厂方法

强制使用工厂方法,通过应用可见性就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型转变成私有的。

### 指针或值作为接收者

如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

change() 接收一个指向 B 的指针,并改变它的内部成员; write() 通过拷贝接收 B 的值并只输出 B 的内容,

type B struct {
      thing int
  }
  
  func (b *B) change() {
      b.thing = 1
  }
  
  func (b B) write() string { 
      return fmt.Sprint(b) 
  }
  
  func main() {
      var b1 B // b1是值
      b1.change()
      fmt.Println(b1.write())
  
      b2 := new(B) // b2是指针
      b2.change()
      fmt.Println(b2.write())
  }
  
  /* 
  {1}
  {1}
  */

欢迎关注我们的微信公众号,每天学习Go知识

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章