何为面向对象
这里给出一个面向对象的定义:
面向对象系统将数据和代码通过“对象”集成到一起,而不是将程序看成由分离的数据和代码组成。对象是数据类型的抽象,它有状态(数据)和行为(代码)
面向对象包括继承、多态、虚派生等特性
类
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())
}
可以见到,NewHero
为一个用于构造Hero
对象的构造函数,Go语言中无默认构造函数,构造函数必须自己创造,一般使用New+{对象(结构体)名}
作为构造函数的名称。
封装
封装是把一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法,如果不想被外界访问,我们大可不必提供方法给外界访问。在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])
}
继承
Go没有继承,而是组合
go有意得被设计为没有继承语法。但这并不意味go中的对象(struct value)之间没有关系,只不过go的作者选择了另外一种机制来暗含这种特性:Go
的设计理念为“组合优于继承”,这一机制也就是组合
Go语言严格遵守composition over inheritance principle的原则。go通过在struct和interface上使用组合和多态来实现继承关系。
Go
并没有原生级别的继承支持,通过结构体内嵌类型的方式实现继承,典型的应用是内嵌匿名结构体类型和内嵌匿名接口类型,这两种方式还有点细微差别:
内嵌匿名结构体类型
说明
将父结构体嵌入到子结构体中,子结构体拥有父结构体的属性和方法,但是这种方式不能支持参数多态。
参数多态的个人理解:对父结构体(对象)的方法的重写
例子
type Person struct {
Name string
Address Address
}
type Address struct {
Number string
Street string
City string
State string
Zip string
}
func (p *Person) Talk() {
fmt.Println("Hi, my name is", p.Name)
}
func (p *Person) Location() {
fmt.Println("I’m at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip)
}
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
p.Talk()
p.Location()
}
Output
Hi, my name is Steve
I’m at 13 Main Gotham NY 01313
组合导致这样的对象关系实际上是in而不是is的关系
很多刚上手Go语言的小白使用了这样的方法后就发现无法修改父结构体(对象)的方法
这时候就需要用到内嵌匿名接口类型
内嵌匿名接口类型
说明
将接口类型嵌入到结构体中,该结构体默认实现了该接口的所有方法,该结构体也可以对这些方法进行重写,这种方式可以支持参数多态,这里要注意一个点是如果嵌入类型没有实现所有接口方法,会引起编译时未被发现的运行错误。
例子
直接拿一个业务场景举例子,假设现在我们现在要给用户发一个通知,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 + "will be sent to admin")
}
func main() {
u := &user{
name: "asong",
email: "你猜",
}
a := &admin{
name: "asong1",
email: "就不告诉你",
}
Send(u)
Send(a)
}
References:
1️⃣ https://segmentfault.com/a/1190000040956053
2️⃣ https://segmentfault.com/a/1190000001832282