6

Go语言鸭子类型“面向对象”编程

 2 years ago
source link: http://www.blackedu.vip/821/go-yu-yan-ya-zi-lei-xing-mian-xiang-dui-xiang-bian/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go语言鸭子类型“面向对象”编程

介绍鸭子类型

我们知道Go语言没有class,也就意味着Go语言没有类和对象,也就无法做到真正意义上的面向对象编程。而面向对象编程非常重要的几个特性封装、继承、重载、多态。其中最重要的两个特性当属继承和多态了。继承可以实现类之间的抽象关系,多态保证了继承以后,可以实现更丰富的功能。

Go语言通过“鸭子类型”的方式,也能实现继承多态。所谓鸭子类型就是1:

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子.

封装与继承

在Go语言中通过结构体的嵌套来模拟继承,结构体相当于封装了类的属性,结构体的方法,相当于类的方法,于是就实现了面向对象的封装和继承。

type Animal struct {
    Name string
    Age  uint8
}

func (an *Animal) Cry() {
    fmt.Println(an.Name, ",I can cry!")
}

type Duck struct {
    Animal
    Type string // 简单分类,卵生动物
}

func (duck *Duck) Cry() {
    fmt.Println(duck.Name, ",I can Cry Ga Ga!")
}

type Dog struct {
    Animal
    Type string // 哺乳动物
}

func (dog *Dog) Cry() {
    fmt.Println(dog.Name, ",I can cry Wow Wow!")
}

封装通过SetGet方法提供属性的修改和访问,方便保护属性的安全。把属性定义小写字母开头,不支持导出即可,这样在别的包使用时,只能通过SetGet方法,而不能直接访问属性值,此处没有具体演示代码。

上面代码演示了“类的继承”,这就是鸭子类型,长得像“类”,有属性和方法,并且还对属性实现了封装,就可以称为“类”。如果子结构体没有覆盖父结构的方法,就会默认调用父类的方法。而通过重写覆盖父类方法可以容易的实现多态。

不过这里有个小问题,继承的子结构与父结构不是一个类型了,虽然每个子结构体都实现了对应的方法,如何进行统一处理呢?

要实现多态还需要借助interface,将上面的Cry方法统一放在interface中。

type Action interface {
    Cry()
}

上面的Animal、Dog、Duck结构体都实现了Cry方法,也是Action类型了。那就可以作为Action来统一处理。

type Cat struct {
    Animal
    Type string
}

func (cat *Cat) Cry() {
    fmt.Println(cat.Name, ", I can cry Miu Miu!")
}

func main() {
    dog := Dog{Animal: Animal{Name: "Jason"}, Type: "哺乳动物"}
    duck := Duck{Animal: Animal{Name: "Tang"}, Type: "卵生动物"}
    cat := Cat{Animal: Animal{Name: "Tom"}, Type: "哺乳动物"}

    var animal = []Action{&dog, &duck}
    animal = append(animal, &cat)

    for _, each := range animal {
        each.Cry()
    }
}

// Jason ,I can cry Wow Wow!
// Tang ,I can Cry Ga Ga!
// Tom , I can cry Miu Miu!

通过上面的例子,基本上实现了面向对象的一些特性,为什么是基本上呢?原因是面向对象编程在对象创建之后,如果没有构造方法会继承父类的构造方法,而Go语言嵌套结构体实例化仍然需要输入父结构体类型,如果多层嵌套,那么初始化的代码将非常难看。

给每个结构体单独写一个构造函数,函数内部完成实例化的动作。比如给DogDuckCat 分别写一个构造函数。这里只写Dog的构造方法,其余类似。

// 构造方法
func NewDog(name, _type string, age uint8) *Dog {
    return &Dog{
        Animal: Animal{Name: name, Age: age},
        Type:   _type,
    }
}

dog := NewDog("Jason", "哺乳动物", 10)
duck := NewDuck("Tang", "卵生动物", 2)
cat := NewCat("Tom", "哺乳动物", 3)

通过构造方法直接实例化结构体.

多继承问题

继承是面向对象中非常重要的概念,上面的代码只实现了单继承,通过匿名嵌套结构体就可以了。如果是多继承,也就是嵌套多个结构体,每个结构体无法避免的存在相同的属性名和方法名,这样在访问的时候就会出问题,是的编译器不会让这种情况发生。

比如定义奇怪的Robot,继承DogCat,具有两种动物的属性和方法,依然使用匿名嵌套的方式定义:

type Robot struct {
    Dog
    Cat
}

r := Robot{Dog: *dog, Cat: *cat}
fmt.Println(r.Dog.Age)
fmt.Println(r.Cat.Age)

如果直接访问属性和访问,编译器报错。最好的方式是定义具名嵌套,这样就不会出错了。

type Robot struct {
    Dog *Dog
    Cat *Cat
}

r := Robot{Dog: dog, Cat: cat}
fmt.Println(r.Dog.Age)
r.Dog.Cry()

看起来使用差不多,实际上代码补全提示时,不会提示非法的属性和方法

本文主要讨论了Go语言通过鸭子类型来实现面向对象编程,可以看到通过组合几种数据结构,基本能够模拟出面向对象的大部分概念,这正是Go语言设计的哲学,语言的设计尽量简洁,去除多余的概念,通过组合来实现更多数据结构和编程方法。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK