GO语言有着面向对象编程的三大特性——封装、继承、多态,只是实现方式不同,本文只介绍实现方式,不会特别详细介绍特性。如果对三大特性没有概念,可以先去看面向对象编程特性相关知识。
一、封装
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作才能对字段进行操作。
注意:GO开发中并没有特别强调封装,这与Java不同,不能总是用Java的语法特性来看待GO,GO本身对面向对象的特性进行了简化。
1、GO体现封装的方式
1)对结构体中的属性进行封装
2)通过方法、包是实现封装
2、工厂模式
GO中的结构体没有构造函数,通常使用工厂模式来实现相同功能。
工厂模式:面向对象编程的设计模式之一。在工厂模式中,创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
以下为GO中实现工厂模式的一个例子:
package pizzaPackage type pizza struct { name string price int } func (p *pizza) SetName(name string) { p.name = name } func (p *pizza) GetName() string { return (*p).name } func (p *pizza) SetPrice(price int) { p.price = price } func (p *pizza) GetPrice() int { return (*p).price } func MakePizza(name string, price int) *pizza { return &pizza{ name: name, price: price, } }
然后在另一个包中调用这个包:
package main import ( "fmt" pizza "project2/main/pizzaPackage" ) func main() { p := pizza.MakePizza("榴莲披萨", 40) fmt.Println("我花", p.GetPrice(), "吃到了", p.GetName()) }
代码执行结果如下:
上面我们创建了一个pizzaPackage包,但是不希望外部直接看到并使用pizza结构体的内部,所以提供了一个工厂方法MakePizza(),其他包调用这个方法就可直接得到一个pizza类型并使用这个结构体所公开的所有方法和属性,但私有的属性和方法是不可以使用的,这其实已经体现了封装的特性。
3、封装的实现步骤
1)将结构体、字段的首字母小写(private)
2)给结构体所在的包提供一个工厂模式的函数,首字母大写
3)提供一个首字母大写的Set方法,用于对属性判断与赋值
4)提供一个首字母大写的Get方法,用于获取属性的值
4、举个例子
其实上述第2点工厂模式的例子中已经体现了封装的特性与步骤,这里不另外举例了。
二、继承
1、GO中继承的实现——嵌套匿名结构体
基本语法:
type 父类结构体名 struct{
field1 type
field2 type
……
}
type 子类结构体名 struct{
父类结构体名
field1 type
……
}
在子类结构体中,父类结构体就是被嵌套的匿名结构体
2、举个例子
type Animal struct {
Name string
Age int
Food string
Drink string
}
type Alive interface {
eat()
drink()
}
func (A *Animal) eat() {
fmt.Println(A.Name, "吃", A.Food)
}
func (A *Animal) drink() {
fmt.Println(A.Name, "喝", A.Drink)
}
type Dog struct {
Animal
}
type Cat struct {
Animal
}
func main() {
dog := Dog{
Animal{
Name: "小狗",
Age: 5,
Food: "骨头",
Drink: "水",
},
}
cat := Cat{Animal{"小猫", 5, "鱼", "牛奶"}}
dog.Animal.drink()
dog.eat()
cat.drink()
cat.Animal.eat()
}
上述代码的输出为:
3、注意事项说明
1)结构体可以使用嵌套结构体的所有方法和字段,不管是否大小写
2)当只嵌套一个匿名结构体时,如果访问嵌套结构体内的字段,内部匿名结构体名可以省略,即A.B.field与A.field都可以;当结构体和匿名结构体有同名字段或方法时,编译器采用就近访问原则
3)当嵌套的匿名结构体数大于等于2时,如果这些匿名结构体有相同字段和方法并且外侧结构体没有时,必须显式的指明所访问内部结构体,即A嵌套了B和C时,A.B.field和A.C.field要指明B、C
4)当嵌套匿名结构体数大于等于2时,就实现了多重继承,但不建议使用
三、多态
GO的多态是通过接口来体现,可以按照统一的接口来调用不同的实现,这个时候接口变量就体现不同的形态。
1、GO中多态的实现——接口体现多态的两种形式
1)多态参数
声明一个接口类型的实例,然后将实现了接口的不同结构体赋值这个接口类型实例,举个例子:
type Animal struct {
Name string
Age int
Food string
Drink string
}
type Alive interface {
eat()
drink()
}
func (A Animal) eat() {
fmt.Println(A.Name, "吃", A.Food)
}
func (A Animal) drink() {
fmt.Println(A.Name, "喝", A.Drink)
}
type Dog struct {
Animal
}
type Cat struct {
Animal
}
func main() {
var life Alive //声明了一个接口变量
animal := Animal{"生物", 10, "食物", "水"}
dog := Dog{
Animal{
Name: "小狗",
Age: 5,
Food: "骨头",
Drink: "水",
},
}
cat := Cat{Animal{"小猫", 5, "鱼", "牛奶"}}
life = animal //将实现了接口的Animal类给接口
life.drink()
life.eat()
life = dog //将实现了接口的Animal类的子类给接口
life.drink()
life.eat()
life = cat
life.drink()
life.eat()
}
上述代码的输出为:
2)多态数组
用接口数组来存放不同的结构体,举个例子:
type Animal struct {
Name string
Age int
Food string
Drink string
}
type Alive interface {
eat()
drink()
}
func (A Animal) eat() {
fmt.Println(A.Name, "吃", A.Food)
}
func (A Animal) drink() {
fmt.Println(A.Name, "喝", A.Drink)
}
type Dog struct {
Animal
}
type Cat struct {
Animal
}
func main() {
var life [3]Alive
life[0] = Dog{
Animal{
Name: "小狗",
Age: 5,
Food: "骨头",
Drink: "水",
},
}
life[1] = Cat{Animal{"小猫", 5, "鱼", "牛奶"}}
life[2] = Animal{"生物", 10, "食物", "水"}
for i := 0; i < 3; i++ {
life[i].eat()
life[i].drink()
}
}
上述代码的输出为:
2、类型断言
由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言
举个简单的例子:
var x interface{}
var b2 float32 = 1.23
x = b2
y := x.(float32) //float32类型断言
fmt.Printf("y的类型是 %T,值是%v",y,y)
一点说明:
在进行类型断言时,如果类型不匹配,就会报panic,因此:
1)进行断言时,要确保原来的空接口指向的就是断言类型
2)或者,进行断言时,进行检测,如果成功就ok,否则也不要报panic,如下:
var x interface{} var b2 float32 = 1.23 x = b2 y := x.(float32) //float32类型断言 if y, ok := x.(float32);ok{ fmt.Printf("y的类型是 %T,值是%v",y,y) } else { fmt.Printf("匹配失败") }
再举一个类型断言的常用示例(在前面Animal例子中加入一个Active()函数):
func Active(life Alive) {
switch life.(type) {
case Dog:
fmt.Println("小狗汪汪叫")
case Cat:
fmt.Println("小猫爬上树")
case Animal:
fmt.Println("动物真可爱")
default:
fmt.Println("不明生命体")
}
}
func main() {
var life [3]Alive
life[0] = Dog{
Animal{
Name: "小狗",
Age: 5,
Food: "骨头",
Drink: "水",
},
}
life[1] = Cat{Animal{"小猫", 5, "鱼", "牛奶"}}
life[2] = Animal{"生物", 10, "食物", "水"}
for i := 0; i < 3; i++ {
Active(life[i])
}
}
上述代码的输出为: