4

总体来看,go语言中的面向对象在使用方式上是灵活易用的,可以说设计理念真的很先进,让人有一种如沐春风的感觉。

如果你在学生时代经历了一个从c到c++的学习历程,你是否还记得,老师会说c++是面向对象的,所以我们不必再使用c中的结构体作为数据结构。我们只需定义的是c++中的类,因为类中不只有成员属性,也有成员函数。换句话说, class是可以完美替代struct的,而且更强大。

回到go中,我们的面向对象使用的就是struct,但时代不同了,这次我们的struct也可以有"成员函数"了。

定义一个典型的面向对象方式


package main
import "fmt"

type Human struct {
    height float32
    weight int
}

func (h Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

func main() {
    person := Human{1.83, 75}
    fmt.Printf("this person's height is %.2f m\n", person.height)
    fmt.Printf("this person's weight is %d kg\n", person.weight)
    fmt.Printf("this person's BMI index is %d\n", person.BMIindex())
}

在main函数中我们初始化了一个Human对象,并分别读取了他们的属性height和weight,最后调用了Human对象的成员函数BMIindex(),通过计算获得了这个人的"体质指数"。

Receiver

上述例子中,一个Human对象的成员函数就是通过Receiver来定义的。我们给一个普通的func添加了Receiver(就是上述示例中的h Human),就构成了一个methodBMIindex()。在这种情况下,这个函数只能依赖于一个Human对象来起作用,而不能被独立调用。其正确的调用方式就是上述的person.BMIindex()

下述又一个例子,我们希望通过定义成员函数来改变对象的成员属性。

package main
import "fmt"

type Human struct {
    height float32
    weight int
}

func (h Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

func (h Human) setHeight(height float32) {
    h.height = height
}

func main() {
    person := Human{1.83, 75}
    fmt.Printf("this person's height is %.2f m\n", person.height)
    fmt.Printf("this person's weight is %d kg\n", person.weight)
    fmt.Printf("this person's BMI index is %d\n", person.BMIindex())
    person.setHeight(1.90)
    fmt.Printf("this person's height is %.2f m\n", person.height)
}

输出结果:
    this person's height is 1.83 m
    this person's weight is 75 kg
    this person's BMI index is 25
    this person's height is 1.83 m

可以看出,我们调用person.setHeight(1.90)之后,person的height属性并没有改变为1.90。而为了解决这个问题,我们需要改变receiver。我们将setHeight()函数定义为下述形式即可。

func (h *Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

原因:我们将对象的指针类型作为receiver,才能改变其属性的值。其本质为,我们可以把receiver看作func的独特的一个参数。如果传递的是一个对象类型,那么函数中改变的实际上是这个对象的副本;而如果传递一个指针类型,改变的才是这个对象本身。

关于receiver的选择,可以这样理解,如果需要获取对象属性(get),则选用对象作为receiver;如果需要改变对象属性(set),则选取指针作为receiver。

method的适用范围

上述示例中,我们定义的method都是对应于struct的,但实际上method的定义可以依赖于所有的自定义类型。所谓自定义类型,就是通过type语句给一些内置类型起了个"别名"后所定义的新类型。


package main
import "fmt"


type Sex string

func (s *Sex) change(){
    if *s == Sex("女") {
        *s = Sex("男")
    }
}

func main() {
    sex := Sex("女")
    fmt.Println("previous sex is ", sex)
    sex.change()
    fmt.Println("current sex is ", sex)    
}

这里,我们新定义了一个类型Sex,并且为其定义了一个method change()。

面向对象中的继承

package main
import "fmt"

type Human struct {
    height float32
    weight int
}

type Woman struct {
    Human
    sex string
}

func (h Human) BMIindex() (index int){
    index = h.weight / int(h.height * h.height)
    return
}

func main() {
    woman := Woman{Human{1.65, 50}, "女"}
    fmt.Printf("this woman's height is %.2f m\n", woman.height)
    fmt.Printf("this woman's wight is %d kg\n", woman.weight)
    fmt.Printf("this woman's BMIindex is %d\n", woman.BMIindex())
}

这个例子展现了Woman对Human的继承。在Woman结构体中包含了匿名字段Human,那么Human中包含的属性也是属于一个Woman对象的属性,Human对象的method同样也可以被Woman对象所使用。值得注意的是,这个method可以被重写。只需要再定义一个以Woman对象类型为receiver的BMIindex(),那么再次调用woman.BMIindex时,实际调用的函数是新定义的这个函数。这就是method的重写。

访问属性

如果你对面向对象中的访问属性很熟悉的话,你一定知道public、private和protected作为访问修饰符的作用。而在go语言中,我们使用大小写来区分。

标识符首字母大写,相当于public属性。这样的成员属性或成员函数可以被在包外部被调用。例如上述Woman、BMIindex。

标识符首字母小写,相当于protected属性。这样的成员属性或成员函数只能在包内部被使用。


Dr_Zhang
156 声望20 粉丝

进击的巨人


下一篇 »
interface