go并不是一个纯面向对象的编程语言。在go中的面向对象,结构体替换了类。
go并没有提供类class,但是它提供了结构体struct,方法method可以在结构体上添加。提供了捆绑数据和方法的行为,这些数据和方法与类类似。
面向对象的基本思想主要体现在封装,继承以及多态等的设计与运用上。下面来看看封装、继承与多态在golang中是如何实现的。
一、封装
- 封装主要是通过访问权限控制实现的。
- 在Java中,共有public 、protected、default、private这四种权限控制。
- 而相应的在golang中,是通过约定来实现权限控制的。变量名首字母大写,相当于java中的public,首字母小写,相当于private。同一个包中访问,相当于default。由于go没有继承,也就没有protected。
在go_project目录下创建model文件,在model文件夹下创建student.go
package model
// student 学生结构体,首字母小写,包外不可见
type student struct {
Name string // 首字母大写,包外可见
age int // 首字母小写,包外不可见
}
// New student的构造函数
func New(name string, age int) *student {
return &student{
Name: name,
age: age,
}
}
// GetAge student的方法,用于外部包访问age字段
func (s *student) GetAge() int {
return s.age
}
// SetAge student的方法,用于外部包修改age字段
func (s *student) SetAge(age int) {
s.age = age
}
在go_project目录下创建main文件夹,在main文件夹下创建main.go
package main
import (
"fmt"
"go_project/model"
)
func main() {
s := model.New("tom", 20)
fmt.Println(s)
// 可以直接访问Name字段
fmt.Println(s.Name)
// 访问不到age字段
// fmt.Println(s.age)
fmt.Println(s.GetAge())
s.Name = "jack"
// 无法直接修改age字段
// s.age = 23
s.SetAge(23)
fmt.Println(s)
}
运行结果
&{tom 20}
tom
20
&{jack 23}
- 在model包里面定义了一个student的结构体;由于首字母小写,所以不可能从其他包中创建student结构体的实例。也就是说没有其他的包能够创建一个零值的student实例。
- 现在导出student实例只能通过构造函数
NewT(parameters)
。如果包只定义了一个类型,那么约定将这个函数命名为New(parameters)而不是NewT(parameters)。 - 由于没有将student的所有字段导出,避免了对student字段的随意访问和修改。只能通过导出的student的
Get
和Set
方法操作。
二、继承
go不支持继承,可以通过将一个struct类型嵌入到另一个结构中实现类似继承效果。
package main
import "fmt"
// Person 结构体 父类
type Person struct {
name string
age int
}
// Say Person的方法,父类方法
func (p Person) Say() {
fmt.Println(p.name, "say hello")
}
// Student 结构体 子类
type Student struct {
Person
school string
}
// Study Student的方法, 子类自己的方法
func (s Student) Study() {
fmt.Println(s.name, "goood good study, day day up")
}
// Say Student的方法,重写父类的方法
func (s Student) Say() {
fmt.Println(s.name, "说:你好!")
}
func main() {
// 创建父类对象
p := Person{
name: "tom",
age: 19,
}
p.Say()
// 创建子类对象
s := Student{
Person: Person{
name: "jack",
age: 18,
},
school: "清华大学",
}
// 访问父类的方法
s.Say()
// 访问子类自己的方法
s.Study()
}
运行结果
tom say hello
jack 说:你好!
jack goood good study, day day up
三、多态
Java 中的多态是通过 extends class 或者 implements interface 实现的,在 golang 中既没有 extends,也没有 implements ,那么 go 中多态是如何实现的呢 ?
答案:在golang中,只要某个struct实现了某个interface中的所有方法,那么我们就认为,这个struct实现了这个接口。
接口类型的变量可以保存实现接口的任何值。接口的这个属性用于实现Go中的多态性。
一个接口的实现:
- 看成实现本身的类型,可以访问实现类中的属性和方法
- 看成对应的接口类型,只能访问接口中的方法
package main
import "fmt"
// AnimalIF 接口
type AnimalIF interface {
Eat()
Sleep()
}
// Animal 结构体 父类
type Animal struct {
name string
age int
}
// Eat Animal的方法
func (a Animal) Eat() {
fmt.Println(a.name, "eat")
}
// Sleep Animal的方法
func (a Animal) Sleep() {
fmt.Println(a.name, "sleep")
}
// Cat 结构体 子类
type Cat struct {
Animal
color string
}
// Eat Cat的方法,重写父类的方法
func (c Cat) Eat() {
fmt.Println("cat eat fish")
}
// Dog 结构体 子类
type Dog struct {
Animal
}
// LookDoor Dog的方法, 子类自己的方法
func (d Dog) LookDoor() {
fmt.Println("dog lookdoor")
}
func main() {
// 创建父类对象
a := Animal{
name: "狮子",
age: 5,
}
a.Eat()
// 创建子类对象
c := Cat{
Animal: Animal{
name: "小花",
age: 3,
},
color: "白色",
}
// 创建子类对象
d := Dog{
Animal: Animal{
name: "旺财",
age: 2,
},
}
// 访问子类拥有的父类的字段
fmt.Println(c.name, c.age)
// 访问子类自己的字段
fmt.Println(c.color)
// 访问父类的方法
d.Eat()
// 访问子类自己的方法
d.LookDoor()
// 访问子类重写的父类的方法
c.Eat()
fmt.Println("--------------------")
// 创建接口类型
var ai AnimalIF
ai = a
ai.Eat()
ai.Sleep()
ai = c
ai.Eat()
ai.Sleep()
ai = d
ai.Eat()
ai.Sleep()
}
运行结果
狮子 eat
小花 3
白色
旺财 eat
dog lookdoor
cat eat fish
--------------------
狮子 eat
狮子 sleep
cat eat fish
小花 sleep
旺财 eat
旺财 sleep
应用举例:
计算一些图形的面积,目前有正方形和圆形。
package main
import (
"fmt"
"math"
)
// Area 面积接口,包含两个方法source和calculate
type Area interface {
source() string
calculate() float64
}
// Square 结构体,正方形
type Square struct {
graphName string
sideLength float64
}
// source Square的方法
func (s Square) source() string {
return s.graphName
}
// calculate Square的方法
func (s Square) calculate() float64 {
return math.Pow(s.sideLength, 2)
}
// Round 结构体,圆形
type Round struct {
graphName string
radius float64
}
// source Round的方法
func (r Round) source() string {
return r.graphName
}
// calculate Round的方法
func (r Round) calculate() float64 {
return math.Pi * math.Pow(r.radius, 2)
}
// totalArea 获取总面积的函数
func totalArea(a []Area) {
var totalArea = 0.0
for _, v := range a {
fmt.Printf("面积来自%v, 它的面积是%v\n", v.source(), v.calculate())
totalArea += v.calculate()
}
fmt.Printf("所有图形的总面积是:%v\n", totalArea)
}
func main() {
graph1 := Square{
graphName: "graph1",
sideLength: 6.3,
}
graph2 := Round{
graphName: "graph2",
radius: 3.4,
}
a := []Area{graph1, graph2}
totalArea(a)
}
运行结果
面积来自graph1, 它的面积是39.69
面积来自graph2, 它的面积是36.316811075498
所有图形的总面积是:76.006811075498
如果再增加长方形的面积,不需要对totalArea()
做任何更改。
首先定义Rectangle
结构体和source()
和calculate()
方法
// Rectangle 结构体, 长方形
type Rectangle struct {
graphName string
length float64
width float64
}
// source Rectangle的方法
func (r Rectangle) source() string {
return r.graphName
}
// calculate Rectangle的方法
func (r Rectangle) calculate() float64 {
return r.length * r.width
}
修改主函数
func main() {
graph1 := Square{
graphName: "graph1",
sideLength: 6.3,
}
graph2 := Round{
graphName: "graph2",
radius: 3.4,
}
graph3 := Rectangle{
graphName: "graph3",
length: 12.3,
width: 6.5,
}
a := []Area{graph1, graph2, graph3}
totalArea(a)
}
运行结果
面积来自graph1, 它的面积是39.69
面积来自graph2, 它的面积是36.316811075498
面积来自graph3, 它的面积是79.95
所有图形的总面积是:155.95681107549802