golang中没有类。golang不是一门纯面向对象编程语言,它没有class(类)的概念,也就没有继承的说法,但Go也可以模拟面向对象的编程方式。在Go中,可以将struct比作其它语言中的class;通过struct定义结构体,表征一类对象,例“type person struct {…}”。
本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。
面向对象三大特征:封装,继承,多态。
Go不是一门纯面向对象编程语言,它没有class(类)的概念,也就没有继承的说法。但Go也可以模拟面向对象的编程方式,即可以将struct比作其它语言中的class。
对象
Go没有class的概念,通过struct定义结构体,表征一类对象。
type person struct { Age int Name string }
对象是状态与行为的有机体。例如下面的java代码:
public class Person { int age; String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
不同于Java,Go的方法不需要跟类的数据绑定在一个class的定义里面,只需要定义在同一个包内。这一点可能初学Go的同学,会感觉很奇怪。
type person struct { Age int Name string } func (p *person) GetAge() int { return p.Age } func (p *person) SetAge(age int) { p.Age = age } func (p *person) GetName() string { return p.Name } func (p *person) SetName(name string) { p.Name = name }
构造函数
Go没有构造函数,对象的数据载体就是一个struct。Java支持构造函数,构造函数名字就跟类名字一样,多个构造函数通过函数重载实现。
而Go构造函数则通过工厂函数进行模拟。实例如下:
type person struct { Age int Name string } /** 构造函数1--通过名字初始化 */ func newPersonByName(name string) *person { return &person{ Name: name, } } /** 构造函数2--通过年龄初始化 */ func newPersonByAge(age int) *person { return &person{ Age: age, } }
需要注意的是,person结构体的名称首字母要小写,避免外部直接越过模拟的构造函数
访问权限
Java有四种访问权限,如下所示:
public | protected |
friendly (default) |
private | |
同一个类 | yes | yes | yes | yes |
同一个包 | yes | yes | yes | no |
不同包子类 | yes | yes | no | no |
不同包非子类 | yes | no | no | no |
Go则做了简化,可见性的最小粒度是包。也就是说,Go保留两种,friendly和public。Go的变量名如果首字母是小写,则代表包内可见;如果首字母是大写,则代表任何地方都可见。
封装
封装,把抽象出来的结构体跟操作结构体内部数据的函数绑定在一起。外部程序只能根据导出的函数API(public方法)修改结构体的内部状态。
封装有两个好处:
隐藏实现:我们只希望使用者直接使用API操作结构体内部状态,而无需了解内部逻辑。就好像一座冰山,我们只看到它露出水面的那一部分。
保护数据:我们可以对数据的修改和访问施加安全措施,调用setter方法的时候,我们可以对参数进行校验;调用getter方法,我们可以增加访问日志等等。
一个简单的bean定义如下所示:
type person struct { Age int Name string } func NewPerson(age int, name string) *person{ return &person{age, name} } func (p *person) SetAge(age int) { p.Age = age } func (p *person) SetName(name string) { p.Name = name } func main() { p:= NewPerson(10, "Lily") p.SetName("Lucy") p.SetAge(18) }
需要注意的是,Go的方法是一种特殊的函数,只是编译器的一种语法糖,编译器瞧瞧帮我们把对象的引用作为函数的第一个参数。例如,下面的代码是等价的
func main() { p:= NewPerson(10, "Lily") p.SetName("Lily1") // 等价于下面的写法 // p是一个引用,函数引用 setNameFunc := (*person).SetName setNameFunc(p, "Lily2") fmt.Println(p.Name) }
继承
继承,子类继承父类,则获得父类的特征和行为。继承的主要目的是为了重用代码。Java实现代码重用的两大利器,就是继承和组合。
Go没有class的概念,谈不上继承。但Go可以通过匿名组合来模拟继承。
如下所示,Cat通过匿名聚合了Animal结构体,就自动获得了Animal的move()和Shout()方法:
type Animal struct { Name string } func (Animal) move() { fmt.Println("我会走") } func (Animal) shout() { fmt.Println("我会叫") } type Cat struct { Animal // 匿名聚合 } func main() { cat := &Cat{Animal{"猫"}} cat.move() cat.shout() }
多态
多态,申明为基类的变量,可以在运行期指向不同的子类,并调用不同子类的方法。多态的目的是为了统一实现。
我们通过接口来实现多态。在java里,我们通过interface来定义接口,通过implements来实现接口。
interface Animal { void move(); void shout(); } class Dog implements Animal { @Override public void move() { System.out.println("我会走"); } @Override public void shout() { System.out.println("我会叫"); } }
而Go则是通过鸭子类型推断,只要某个对象长得想鸭子,叫起来像鸭子,那么它就是鸭子。也就是说,Go的接口是比较隐匿的,只要某个对象实现来接口申明的所有方法,那么就认为它属于该接口。
type Animal interface { move() shout() } type Cat struct { Animal // 匿名聚合 } func (Cat)move() { fmt.Println("猫会走") } func (Cat)shout() { fmt.Println("猫会叫") } type Dog struct { Animal // 匿名聚合 } func (Dog)move() { fmt.Println("狗会走") } func (Dog)shout() { fmt.Println("狗会叫") } func main() { cat := Cat{} dog := Dog{} // 申明接口数组 animals := []Animal{cat, dog} for _,ele := range animals { // 统一访问 ele.move() ele.shout() } }
【