简介:本文讨论在设计一个包的导出接口时遇到的问题以及所采取的解决思路和方法,并提供了模拟代码作为例子。
假设有一个包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相互独立,各司其职,同时也易于使用。
本文分享自微信公众号 - Golang语言社区(Golangweb)
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2016-08-12
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句