用Go写出面向对象风格的代码
2021-11-15
go
go
字数统计: 1.7k(字)
阅读时长: 7(分)
前言
面向对象程序设计是一种计算机编程架构,英文全称:Object Oriented Programming,简称OOP。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成,OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念就是类和对象。
这一段话在网上介绍什么是面向对象编程时经常出现,大多数学习Go语言的朋友应该也都是从C++、python、java转过来的,所以对面向对象编程的理解应该很深了,所以本文就没必要介绍概念了,重点来看一下如何使用Go语言来实现面向对象编程的编程风格。
类
Go语言本身就不是一个面向对象的编程语言,所以Go语言中没有类的概念,但是他是支持类型的,因此我们可以使用struct类型来提供类似于java中的类的服务,可以定义属性、方法、还能定义构造器。来看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type Hero struct { Name string Age uint64 } func NewHero () *Hero { return &Hero{ Name: "盖伦" , Age: 18 , } } func (h *Hero) GetName() string { return h.Name } func (h *Hero) GetAge() uint64 { return h.Age } func main () { h := NewHero() print (h.GetName()) print (h.GetAge()) }
这就一个简单的 "类"的使用,这个类名就是Hero,其中Name、Age就是我们定义的属性,GetName、GetAge这两个就是我们定义的类的方法,NewHero就是定义的构造器。因为Go语言的特性问题,构造器只能够依靠我们手动来实现。
这里方法的实现是依赖于结构体的值接收者、指针接收者的特性来实现的。
封装
封装是把一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法,如果不想被外界访问,我们大可不必提供方法给外界访问。在Go语言中实现封装我们可以采用两种方式:
Go语言支持包级别的封装,小写字母开头的名称只能在该包内程序中可见,所以我们如果不想暴露一些方法,可以通过这种方式私有包中的内容,这个理解比较简单,就不举例子了。
Go语言可以通过 type 关键字创建新的类型,所以我们为了不暴露一些属性和方法,可以采用创建一个新类型的方式,自己手写构造器的方式实现封装,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 type IdCard string func NewIdCard (card string ) IdCard { return IdCard(card) } func (i IdCard) GetPlaceOfBirth() string { return string (i[:6 ]) } func (i IdCard) GetBirthDay() string { return string (i[6 :14 ]) }
声明一个新类型IdCard,本质是一个string类型,NewIdCard用来构造对象,
GetPlaceOfBirth、GetBirthDay就是封装的方法。
继承
Go并没有原生级别的继承支持,不过我们可以使用组合的方式来实现继承,通过结构体内嵌类型的方式实现继承,典型的应用是内嵌匿名结构体类型和内嵌匿名接口类型,这两种方式还有点细微差别:
内嵌匿名结构体类型:将父结构体嵌入到子结构体中,子结构体拥有父结构体的属性和方法,但是这种方式不能支持参数多态。
内嵌匿名接口类型:将接口类型嵌入到结构体中,该结构体默认实现了该接口的所有方法,该结构体也可以对这些方法进行重写,这种方式可以支持参数多态,这里要注意一个点是如果嵌入类型没有实现所有接口方法,会引起编译时未被发现的运行错误。
内嵌匿名结构体类型实现继承的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 type Base struct { Value string } func (b *Base) GetMsg() string { return b.Value } type Person struct { Base Name string Age uint64 } func (p *Person) GetName() string { return p.Name } func (p *Person) GetAge() uint64 { return p.Age } func check (b *Base) { b.GetMsg() } func main () { m := Base{Value: "I Love You" } p := &Person{ Base: m, Name: "asong" , Age: 18 , } fmt.Print(p.GetName(), " " , p.GetAge(), " and say " ,p.GetMsg()) }
上面注释掉的方法就证明了不能进行参数多态。
内嵌匿名接口类型实现继承的例子
直接拿一个业务场景举例子,假设现在我们现在要给用户发一个通知,web、app端发送的通知内容都是一样的,但是点击后的动作是不一样的,所以我们可以进行抽象一个接口OrderChangeNotificationHandler来声明出三个公共方法:GenerateMessage、GeneratePhotos、generateUrl,所有类都会实现这三个方法,因为web、app端发送的内容是一样的,所以我们可以抽相出一个父类OrderChangeNotificationHandlerImpl来实现一个默认的方法,然后在写两个子类WebOrderChangeNotificationHandler、AppOrderChangeNotificationHandler去继承父类重写generateUrl方法即可,后面如果不同端的内容有做修改,直接重写父类方法就可以了,来看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 type Photos struct { width uint64 height uint64 value string } type OrderChangeNotificationHandler interface { GenerateMessage() string GeneratePhotos() Photos generateUrl() string } type OrderChangeNotificationHandlerImpl struct { url string } func NewOrderChangeNotificationHandlerImpl () OrderChangeNotificationHandler { return OrderChangeNotificationHandlerImpl{ url: "https://base.test.com" , } } func (o OrderChangeNotificationHandlerImpl) GenerateMessage() string { return "OrderChangeNotificationHandlerImpl GenerateMessage" } func (o OrderChangeNotificationHandlerImpl) GeneratePhotos() Photos { return Photos{ width: 1 , height: 1 , value: "https://www.baidu.com" , } } func (w OrderChangeNotificationHandlerImpl) generateUrl() string { return w.url } type WebOrderChangeNotificationHandler struct { OrderChangeNotificationHandler url string } func (w WebOrderChangeNotificationHandler) generateUrl() string { return w.url } type AppOrderChangeNotificationHandler struct { OrderChangeNotificationHandler url string } func (a AppOrderChangeNotificationHandler) generateUrl() string { return a.url } func check (handler OrderChangeNotificationHandler) { fmt.Println(handler.GenerateMessage()) } func main () { base := NewOrderChangeNotificationHandlerImpl() web := WebOrderChangeNotificationHandler{ OrderChangeNotificationHandler: base, url: "http://web.test.com" , } fmt.Println(web.GenerateMessage()) fmt.Println(web.generateUrl()) check(web) }
因为所有组合都实现了OrderChangeNotificationHandler类型,所以可以处理任何特定类型以及是该特定类型的派生类的通配符。
多态
多态是面向对象编程的本质,多态是支代码可以根据类型的具体实现采取不同行为的能力,在Go语言中任何用户定义的类型都可以实现任何接口,所以通过不同实体类型对接口值方法的调用就是多态,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 type SendEmail interface { send() } func Send (s SendEmail) { s.send() } type user struct { name string email string } func (u *user) send() { fmt.Println(u.name + " email is " + u.email + "already send" ) } type admin struct { name string email string } func (a *admin) send() { fmt.Println(a.name + " email is " + a.email + "already send" ) } func main () { u := &user{ name: "asong" , email: "你猜" , } a := &admin{ name: "asong1" , email: "就不告诉你" , } Send(u) Send(a) }