sprintf函数实现_从Go结构成员的升格到面向对象类的实现

6059fddd636e29921840fbc0ed50807c.png

我喜欢写这样的短文,每次说一个点,内容不多不少,读者读起来也不会感觉到累,而可以确实get到了东西。。。

今天我们来谈谈Go语言的结构(struct)成员的升格(promotion)。

※ 我本来是想用升级来翻译promotion的,然后觉得不行,让人感觉在玩RPG打小怪升级。。。提升这个词也不太好,这明显是个动词。。。暂时用升格不知道大家同意吗?有升级的意思,又能体会到跨结构层次的一个质变过程。


结构成员嵌套

要谈成员升格,就不得不提到,Go结构的成员是可以嵌套的。考虑下面这个嵌套的例子

package main

import "fmt"

type Wheel struct {
	brand string
	code  string
}

type Vehicle struct {
	brand     string
	model     string
	numWheels uint8
	wheel     Wheel
}

func (v Vehicle) String() string {
	return fmt.Sprintf("brand: %vnmodel: %vnnumber of wheels: %vnwheel brand: %vnwheel code: %vn",
		v.brand, v.model, v.numWheels, v.wheel.brand, v.wheel.code)
}

func main() {

	car := Vehicle{
		brand:     "VOLVO",
		model:     "V60",
		numWheels: 4,
		wheel: Wheel{
			brand: "Continental",
			code:  "235-45-19",
		},
	}

	fmt.Println(car)

}

运行结果如下

brand: VOLVO
model: V60
number of wheels: 4
wheel brand: Continental
wheel code: 235-45-19

例子很简单(所以就不写注释了),定义了两个结构,一个是车辆,一个是轮胎,每一种车都有默认的出厂标配轮胎,所以这是一个嵌套结构。

在这个例子里为了方便打印,我实现了fmt的Stringer接口函数String。大家可以看到,为了能够获取轮胎成员的值,我使用了

v.wheel.brand

这样的语句。v是一辆车,wheel是指它的轮胎,而v.wheel.brand是指它的轮胎的商标。


升格产生于成员匿名化

我们其实也可以把车辆结构里的wheel成员匿名化,考虑上例的修改版

package main

import "fmt"

type Wheel struct {
	brand string
	code  string
}

type Vehicle struct {
	brand     string
	model     string
	numWheels uint8
	Wheel
}

func (v Vehicle) String() string {
	return fmt.Sprintf("brand: %vnmodel: %vnnumber of wheels: %vnwheel brand: %vnwheel code: %vn",
		v.brand, v.model, v.numWheels, v.Wheel.brand, v.code)
}

func main() {

	car := Vehicle{
		brand:     "VOLVO",
		model:     "V60",
		numWheels: 4,
		Wheel: Wheel{
			brand: "Continental",
			code:  "235-45-19",
		},
	}

	fmt.Println(car)

}

运行结果同上

这里要注意两点:

  1. 结构成员匿名化会使得该成员的类型名变成变量名,所以当我们想要取得v的轮胎的成员时,可以使用 v.Wheel.brand (例子里用了) 和 v.Wheel.code (例子里没有用这个,但也可以);
  2. 当结构成员的子成员名字和外界没有冲突的时候,我们可以给它升格,也就是说 v.Wheel.code 和 v.code 都可以使用,因为Vehicle的直系成员里没有code这个变量名!这就是Go的一个语法糖。

为什么称之为语法糖呢?因为现实中,我们会遇到各种结构嵌套,加那么多点也许会很费劲,这是一个比较官方的解释。。。

但是我个人认为,这是一种实现继承的方法!但是要求变量名请不要重复。当然你用其他面向对象语言写类型继承的时候,也不会让父类和子类的变量名有重复的。所以我们有下面的示例:

type ParentClass struct {
	A int
	B int
}

type ChildClass struct {
	X string
	Y string
	ParentClass // 这里的匿名成员可以看成是一种继承关系
}

所以,升格的意义很大。


接口成员

我们都知道,Go结构的成员也可以是一个接口。再次修改第一个例子

package main

import "fmt"

//接口定义
type Wheeler interface {
	getWheel() string
}

type Wheel struct {
	brand string
	code  string
}

//实现这个接口
func (w Wheel) getWheel() string {
	return "wheel brand: " + w.brand + "nwheel code: " + w.code + "n"
}

type Vehicle struct {
	brand     string
	model     string
	numWheels uint8
	//接口成员
	wheel Wheeler
}

func (v Vehicle) String() string {
	return fmt.Sprintf("brand: %vnmodel: %vnnumber of wheels: %vn%v",
		v.brand, v.model, v.numWheels, v.wheel.getWheel())
}

func main() {

	car := Vehicle{
		brand:     "VOLVO",
		model:     "V60",
		numWheels: 4,
		wheel: Wheel{
			brand: "Continental",
			code:  "235-45-19",
		},
	}

	fmt.Println(car)

}

运行结果同上。

请注意,新加入的代码都有注释。

在这个例子里,我让wheel成员指向了Wheel的对象,并且通过接口实现了打印轮胎的方法,这样代码的可读性和重复利用率会大大提高,其实封装性也更好,而且也适合于拆分成一个一个小的library。

但是,我还是要说,这给实现了类提供了希望。。。下面的匿名接口成员给出解释。


接口成员也可以匿名并升格

同样的,考虑上例的修改版

package main

import "fmt"

type Wheeler interface {
	getWheel() string
}

type Wheel struct {
	brand string
	code  string
}

func (w Wheel) getWheel() string {
	return "wheel brand: " + w.brand + "nwheel code: " + w.code + "n"
}

type Vehicle struct {
	brand     string
	model     string
	numWheels uint8
	//接口成员
	Wheeler
}

func (v Vehicle) String() string {
	//这里使用v.getWheel()就好
	return fmt.Sprintf("brand: %vnmodel: %vnnumber of wheels: %vn%v",
		v.brand, v.model, v.numWheels, v.getWheel())
}

func main() {

	car := Vehicle{
		brand:     "VOLVO",
		model:     "V60",
		numWheels: 4,
		Wheeler: Wheel{
			brand: "Continental",
			code:  "235-45-19",
		},
	}

	fmt.Println(car)

}

运行结果还是同上。

看到没有?接口名变成了成员名,后面则使用 v.getWheel() 就好,因为该方法(method)被升格了。

注意到没有?v.getWheel() 是多像一个类的对象在调用自己的成员函数啊。。。其实就是一回事。

你可能要问,我们能不能通过 v.Wheeler 来调用Wheel结构里的成员呢?因为Wheeler只是一个接口,所以答案是不可以!

所以我们可以利用这个原理来实现封装。以下代码给了一个封装的模版,其中的私有成员 val 是不可以被外界直接访问的:

package main

import "fmt"

//虽然这个结构是可以被export的
//但是其成员不能,所以是私有的
type PrivatePart struct {
	val int
}

type PrivateMethods interface {
	Init(x int)
	GetX() int
	SetX(x int)
}

//初始化函数也是可以实现的
func (p *PrivatePart) Init(x int) {
	p.val = x
}

//注意,这个函数不符合Go语言命名惯例
//正常情况Getter应该就是 X()
//我们这里情况特殊,是为了模仿其他面相对象语言
func (p PrivatePart) GetX() int {
	return p.val
}

func (p *PrivatePart) SetX(x int) {
	p.val = x
}

//这就是我们要创建的符合面向对象风格的类
type SomeClass struct {
	PrivateMethods
}

func main() {
	object := SomeClass{&PrivatePart{}}

	object.Init(1)

	fmt.Println("The value is equal to ", object.GetX())

	object.SetX(2)

	fmt.Println("The value is equal to ", object.GetX())
}

运行结果如下:

The value is equal to  1
The value is equal to  2

这篇短文的核心在这里,虽然Go没有类,但是,我们依然可以用匿名加升格的方式实现它!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值