Go语言面向对象编程
Go语言的面相对象和主流语言有很大的不同,如Go 语言里是不支持继承,Go语言的接口实现也不太一样
1. 数据和行为的封装
type Employee struct {
Id string
Name string
Age string
}
func TestStruck (t *testing.T) {
//初始化一个结构体
e := Employee{"1","李比","20"}
e1 := Employee{Id:"2",Age:"23"}
//使用new关键字返回一个指向这个结构体的指针,相当于使用了取地址符"&"
e2 := new(Employee)
e2.Age = "30"
}
PS:使用new
关键字会返回结构体的指针、使用.
就可以直接访问指针指向的结构体内的元素
- 行为(方法)的定义
在这个例子里,我们任然使用上面已经定义好了的结构体。定义一个结构体的行为只要在这个函数前面
type Employee struct {
Id string
Name string
Age string
}
//使用这种方法定义的行为在使用自己(this)的时候会进行值的复制
func (e Employee) getString() string {
return e.Id+","+e.Name+","+e.Age
}
//使用指针传入的话就会避免内存拷贝
func (e *Employee) getStringPtr() string {
return e.Id+","+e.Name+","+e.Age
}
2. 接口
//=========接口定义==========
type Programmer interface {
writeCode() string
}
//=========接口实现==========
type GoProgrammer struct {
}
func (g *GoProgrammer) writeCode() string {
return "fmt.println(\"hello world\")"
}
可以看见,Go语言的接口实现完全没有看见依赖。实现接口的结构体只需要拥有和接口一样函数名和函数签名的函数就可以认为他实现了接口
因为接口的实现是非入侵性的,Go可以实现先定义实例,后面再去提出公共部分
func TestInterface(t *testing.T) {
var programmer Programmer
programmer = new(GoProgrammer)
t.Log(programmer.writeCode())
}
3. 扩展与复用
用于Go不支持继承,因此Go语言的扩展也变成了Go语言里争议最大的部分
//======父类 宠物类======
type Pat struct {
}
func (p *Pat) speak() {
fmt.Println("...")
}
func (p *Pat) speakTo(host string) {
p.speak()
fmt.Println(host)
}
//======子类 狗????=======
type Dog struct {
Pat //在这里声明一个“父类“,这个结构体就可以拥有父类的方法
}
func (d *Dog) speak() {
fmt.Println("旺!")
}
//======程序入口======
func TestDog(t *testing.T) {
//var p Pat = new(Dog)
d := new(Dog)
d.speakTo("123") // ... \n 123
}
从上面的程序可以看出,即时使用了内嵌的匿名成员变量来代替继承,但是在程序入口里可以看出,这种做法没有实现向上转型和重载
4. 多态
下面是一个多态的例子
//=========接口定义==========
type Programmer interface {
writeCode() string
}
//=========接口实现==========
type GoProgrammer struct {
}
func (g *GoProgrammer) writeCode() string {
return "fmt.println(\"hello world\")"
}
//======新的Programmer类型实现=====
type JavaProgrammer struct {
}
func (j *JavaProgrammer) writeCode() string {
return "System.out.println(\"hello world\");"
}
//=======一个普通的方法,传入Programmer类型的数据
func work(programmer Programmer) {
fmt.Printf("Type:%T,Code:%s\n",programmer,programmer.writeCode())
}
//======程序入口======
func TestPolymorphism(t *testing.T) {
java := new(JavaProgrammer)
goo := new(GoProgrammer)
work(java)
work(goo)
}
-
空接口
interface{}
可以表示任何类型(相当于java的Object或C中的void*)
- 接口定义的规范
- 倾向于把接口定义得越小越好,如果徐需要使用大接口,那么我们使用多个小接口组合而成,下面就是一个组合接口的事例
type Interface1 interface{
...
}
type Intercace2 interface{
...
}
type BigInterface interface{
Interface1
Interface2
}
Go语言的异常机制
Go语言没有传统意义上的错误机制,Go 语言的创始人认为程序员对异常的使用对代码的影响会很大,他们更喜欢C一样的对返回值的判断;并且Go支持多返回值,因此可以通过多返回一个错误码来处理异常
-
errer接口
这是一个Go语言内置的接口,只有一个方法。他的定义如下
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
我们在函数里可以另外抛出一个实现上面的接口的结构体。可以使用errors.New("错误说明")
的方式来快速创建一个错误类(结构体)
// 斐波纳切数列
func GetFib(n int) ([]int, error) {
if n<0 || n>100{
return nil,errors.New("输入的数不符合规定")
}
fibList := []int {1,1}
for i:=2;i<n;i++{
fibList = append(fibList,fibList[i-2]+fibList[i-1])
}
return fibList,nil
}
-
panic和recover
和上面的error接口一样,他们都是Go语言提供的用于处理异常的工具。panic的调用方法是panic(interface{})
,recover的调用方法是recover()
- 一般用于不可恢复的错误中断程序
- 和
os.Exit(int)
方法不同,panic(interface{})
会调用defer指定的函数,也会输出当前调用栈的信息(相当于java的异常类被抛出)
- 用于掩盖一个错误,使用recover函数会拿到panic抛出的异常,并且在defer函数里处理这个异常
- 在使用这种异常处理机制的时候需要小心由于在异常处理块(recover)没有有效的处理异常甚至只是打印在日志里而导致自己的程序编程"僵尸进程"。有时候直接让程序抛出异常也不失为一种比较好的选择
- 下面是一个关于panic和recover的示例(相当于Java中的try、catch)
func TestError(t *testing.T) {
defer func () {
if err := recover();err != nil {
fmt.Println("异常被掩盖:",err)
}
fmt.Println("运行defer")
}()
fmt.Println("开始执行")
//出现不可调和的错误,终止程序
panic(errors.New("出现异常"))
fmt.Printf("执行结束")
}
运行结果如下
=== RUN TestError
开始执行
异常被掩盖: 出现异常
运行defer
--- PASS: TestError (0.00s)
PASS