原文链接:如何应用 Go 语言写出面向对象格调的代码
前言
哈喽,大家好,我是
asong
。在上一篇文章:小白也能看懂的 context 包详解:从入门到精通 剖析context
的源码时,咱们看到了一种编程办法,在构造体里内嵌匿名接口,这种写法对于大多数初学Go
语言的敌人看起来是懵逼的,其实在构造体里内嵌匿名接口、匿名构造体都是在面向对象编程中继承和重写的一种实现形式,之前写过java
、python
对面向对象编程中的继承和重写应该很相熟,然而转Go
语言后写出的代码都是面向过程式的代码,所以本文就一起来剖析一下如何在Go
语言中写出面向对象的代码。
面向对象程序设计是一种计算机编程架构,英文全称:Object Oriented Programming,简称 OOP。OOP 的一条根本准则是计算机程序由单个可能起到子程序作用的单元或对象组合而成,OOP 达到了软件工程的三个次要指标:重用性、灵活性和扩展性。OOP= 对象 + 类 + 继承 + 多态 + 音讯,其中外围概念就是类和对象。
这一段话在网上介绍什么是面向对象编程时经常出现,大多数学习 Go
语言的敌人应该也都是从 C++
、python
、java
转过来的,所以对面向对象编程的了解应该很深了,所以本文就没必要介绍概念了,重点来看一下如何应用 Go
语言来实现面向对象编程的编程格调。
类
Go
语言自身就不是一个面向对象的编程语言,所以 Go
语言中没有类的概念,然而他是反对类型的,因而咱们能够应用 struct
类型来提供相似于 java
中的类的服务,能够定义属性、办法、还能定义结构器。来看个例子:
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
关键字创立新的类型,所以咱们为了不裸露一些属性和办法,能够采纳创立一个新类型的形式,本人手写结构器的形式实现封装,举个例子:
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
并没有原生级别的继承反对,不过咱们能够应用组合的形式来实现继承,通过构造体内嵌类型的形式实现继承,典型的利用是内嵌匿名构造体类型和内嵌匿名接口类型,这两种形式还有点细微差别:
- 内嵌匿名构造体类型:将父构造体嵌入到子结构体中,子结构体领有父构造体的属性和办法,然而这种形式不能反对参数多态。
- 内嵌匿名接口类型:将接口类型嵌入到构造体中,该构造体默认实现了该接口的所有办法,该构造体也能够对这些办法进行重写,这种形式能够反对参数多态,这里要留神一个点是如果嵌入类型没有实现所有接口办法,会引起编译时未被发现的运行谬误。
内嵌匿名构造体类型实现继承的例子
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())
//check(p)
}
下面正文掉的办法就证实了不能进行参数多态。
内嵌匿名接口类型实现继承的例子
间接拿一个业务场景举例子,假如当初咱们当初要给用户发一个告诉,web
、app
端发送的告诉内容都是一样的,然而点击后的动作是不一样的,所以咱们能够进行形象一个接口 OrderChangeNotificationHandler
来申明出三个公共办法:GenerateMessage
、GeneratePhotos
、generateUrl
,所有类都会实现这三个办法,因为 web
、app
端发送的内容是一样的,所以咱们能够抽相出一个父类 OrderChangeNotificationHandlerImpl
来实现一个默认的办法,而后在写两个子类 WebOrderChangeNotificationHandler
、AppOrderChangeNotificationHandler
去继承父类重写 generateUrl
办法即可,前面如果不同端的内容有做批改,间接重写父类办法就能够了,来看例子:
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
语言中任何用户定义的类型都能够实现任何接口,所以通过不同实体类型对接口值办法的调用就是多态,举个例子:
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)
}
总结
归根结底面向对象编程就是一种编程思维,只不过有些语言在语法个性方面更好的为这种思维提供了反对,写出面向对象的代码更容易,然而写代码的还是咱们本人,并不是咱们用了 java
就肯定会写出更形象的代码,在工作中我看到用 java
写出面向过程式的代码不胜其数,所以无论用什么语言,咱们都应该思考如何写好一份代码,大量的形象接口帮忙咱们精简代码,代码是优雅了,但也会面临着可读性的问题,什么事都是有两面性的,写出好代码的路还很长,还须要一直摸索 …………。
文中示例代码曾经上传github
:https://github.com/asong2020/…
欢送关注公众号:Golang 梦工厂