设计包导出接口的随想

时间:2022-05-05
本文章向大家介绍设计包导出接口的随想,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

简介:本文讨论在设计一个包的导出接口时遇到的问题以及所采取的解决思路和方法,并提供了模拟代码作为例子。

假设有一个包gameword有个导出结构Player,包含了一些游戏逻辑相关的函数;而且这个Player是可以序列化的。很直接的想法是Player直接实现io.ReadWriter接口,类似这样:

// version 1
package gameworld

type Player struct {


// ...
}
// 序列化函数
func (p *Player) Read(data []byte) (int, error) {

// ...
}
func (p *Player) Write(data []byte) (int, error) {

// ...
}
// 游戏逻辑函数
func (p *Player) Walk() {

// ...
}

这里有一个很明显的问题是:在Player暴露给包使用者的函数中,Read和Write函数是为了序列化而存在的,和Walk等游戏逻辑相关的函数根本没有直接的关联;这样把不同类别的函数都放在Player里大大减弱了对象的内聚性,也对使用者产生了干扰。但是Player又需要实现序列化,也就是要提供io.ReadWriter接口。解决的办法是提供一个全局转换函数,用于把Player对象转换成io.ReadWriter接口。这个转换函数是全局的而不是Player的一个函数,理由同样是为了保持Player对象的内聚性。
// version 2
package gameworld
import "io"
type Player struct {

// ...
}
// 游戏逻辑函数
func (p *Player) Walk() {

// ...
}
// 序列化实现
type playerReadWriter Player
func (p *playerReadWriter) Read(data []byte) (int, error) {

// ...
}
func (p *playerReadWriter) Write(data []byte) (int, error) {

// ...
}
// 转换函数
func SerializePlayer(p *Player) io.ReadWriter {

return (*playerReadWriter)(p)
}

最后,为了使用上的便利,最好能有一个Size函数能够知道player序列化所需要缓冲区字节数的大小。因此把io.ReadWriter和这个Size函数整合成一个新的序列化接口。于是,有了版本3:

// version 3
package gameworld
type Player struct {
// ...
}
// 游戏逻辑函数
func (p *Player) Walk() {


// ...

}
// 序列化实现
type playerReadWriter Player
func (p *playerReadWriter) Read(data []byte) (int, error) {


// ...

}
func (p *playerReadWriter) Write(data []byte) (int, error) {


// ...

}
func (p *playerReadWriter) Size() int {


// ...

}
// 序列化接口
type ReadWriter interface {


Read([]byte) (int, error)


Write([]byte) (int, error)


Size() int

}
// 转换函数
func SerializePlayer(p *Player) ReadWriter {


return (*playerReadWriter)(p)

}
至此,暴露给包外的界面非常的清晰。Player、SerializePlayer和ReadWriter相互独立,各司其职,同时也易于使用。