面向对象定义
面向对象三要素:
- 封装:将属性(数据)和方法(操作)封装,提供访问控制,隐藏实现细节,暴露该暴露的
- 继承:子类可以从父类直接获得属性和方法,减少重复定义。子类中如果与父类不同,可以自己定 义新的属性和方法,也可以覆盖同名的属性和方法
- 多态:前提是继承和覆盖,使得子类中虽然使用同一个方法,但是不同子类表现不同,就是不同的态
实现了以上特征的语言,才能成为面向对象编程范式语言。 严格意义来说,Go语言就是不想实现面向对象编程范式。但是面向对象又有一些不错的特性,Go语言 通过组合的方式实现了类似的功能。 只能说,Go语言实现了一种非常有自我特征的面向对象。
封装
通过结构体,可以把数据字段封装在内,还可以为结构体提供方法。
访问控制:
- 属性、方法标识符首字母大写,实现了对包外可见的访问控制
- 属性、方法标识符首字母小写,仅包内可见
- 这些一定程度上实现了public、private的访问控制
构造函数
Go没有提供类似C++、Java一样的构造函数、析构函数。在Go中,用构造结构体实例的函数,这个函数 没有特别的要求,只要返回结构体实例或其指针即可(建议返回指针,不然返回值会拷贝)。习惯上, 构造函数命名是New或new开头。如果有多个构造函数,可以使用不同命名函数,因为Go也没有函数重载。通过不同的函数名来模拟构造函数重载。
type Animal struct { name string age int } func NewDefaultAnimal() *Animal { return &Animal{"nobody", 1} } func NewAnimal(name string, age int) *Animal { return &Animal{name, age} }
继承
Go语言没有提供继承的语法,实际上需要通过匿名结构体嵌入(组合)来实现类似效果。
package main import "fmt" type Animal struct { name string age int } func (*Animal) run() { fmt.Println("Animal run~~~") } type Cat struct { Animal // 匿名结构体嵌入 color string } func main() { cat := new(Cat) cat.run() cat.Animal.run() }
通过匿名结构体嵌入,子结构体就拥有了父结构体的属性name、age,和run方法。
覆盖
覆盖override,也称重写。不是重载overload。
// 为Cat增加一个run方法,这就是覆盖。 func (*Cat) run() { fmt.Println("Cat run+++") }
上例增加run方法是完全覆盖,就是不依赖父结构体方法,重写功能。 如果是依赖父结构体方法,那就要在子结构体方法中显式调用它。
func (c *Cat) run() { c.run() // 这里不可这样写,成递归了 c.Animal.run() // 这是调用父结构体方法。 fmt.Println("Cat run+++") }
多态
Go语言不能像Java语言一样使用多态,但可以通过引入interface接口来解决。
package main import "fmt" type Runner interface { run() } type Animal struct { name string age int } func (*Animal) run() { fmt.Println("Animal run~~~") } type Cat struct { Animal // 匿名结构体嵌入 color string } func (c *Cat) run() { c.Animal.run() fmt.Println("Cat run+++") } type Dog struct { Animal // 匿名结构体嵌入 color string } func (d *Dog) run() { d.Animal.run() fmt.Println("Dog run+++") } func test(a Runner) { // 多态 a.run() } func main() { // var a Animal = Cat{} // Go做不到这样赋值 // a.run() // Go无法写出这2行,用接口 d := new(Dog) d.name = "snoopy" test(d) c := new(Cat) c.name = "Garfield" test(c) test(d) }
test使用同一个类型的同一个接口却表现不同,这就是多态。