init函数是Golang中的一种包级别初始化函数,在包被引用时执行,且只执行一次。
init函数的这种特性被广泛使用,其中最常见的一种用法是基于此的一种工厂模式。
工厂模式我的理解是希望即便在不同的条件下,通过一套标准化的流程和代码都可以顺利完成代码设计的功能
以image库的Decode函数来做个例子,我们希望系统可以支持不同类型的图像的解码,这种解码应该代码层面基本维持一致。
这就是一个标准的函数入口了,你把一个不管什么类型图像的reader传进去,他就能解析出通用的image图像,以及他的类型,以及可能的错误,无论你是jpeg文件还是png文件,都可以直接传进去,他就无论类型都可以解析出来。
那image库是怎么知道这个图片的类型的呢,我们打开这个函数的代码,发现他调用了一个sniff函数来探测类型,而这个sniff函数是怎么工作的呢:
这个sniff函数似乎是从一个数组中获得了支持的所有类型,并在这些类型中做判断,返回一个匹配的类型。
试用decode
那我们写一个简单的测试来测试一下
发现输出是image: unknown format
这时,我们稍微在import里面加上一个引用
好,输出就变成了png。
匿名引用和init函数的关系
为什么一个匿名引用就可以让image库使用到原本不能使用的png功能?
原来,在png包的reader.go里面最下方有一个init函数,内容如下:
这个内容显而易见,是说往image库注册一个新的类型,叫png类型,对应的头部信息是……,解码类是……
那结合上面关于init的解释,我们可以看出整个工厂模式的逻辑如下:
利用init函数在引用时优先执行的特性,我们可以确保在调用工厂类(image)的具体函数(Decode)之前,具体的执行类(png)完成了注册工作(RegisterFormat),因此,只要有匿名引用在,我们就不会出image: unknown format错误。
如何利用这种工厂模式
那这种所谓的工厂模式又具体能拿来干什么呢?通常他的用法类似image这种模式,就是一堆子包Register,工厂包有个Map或者Array,做一个按名称选择的入口函数,如果没有包大小的限制,可以全量做匿名引用,好处就是单纯的代码层面的分层结构清晰。
也可以通过在main中对按需进行不同类型的引用,来扩展支持的文件类型,要用png就引用png,要用jpeg就引用jpeg。好处是动态可扩展,代码层面不需要再做调整,只改import。使得系统的动态扩展变得很容易。
那我们还可以配合go build和编译标签,在这种扩展能力上,加上适配特定平台的目的。
例如我们可以写types_linux.go和types_windows.go来使得系统在不同系统下调用不同的支持库。
上面利用了golang的自动分系统编译的功能,还可以利用编译标签,来达到更广泛的分平台编译功能
比如上面的代码配合
就可以编译一个aliyun特定的版本
当然这个模式的一个问题是,你完全有可能忘了进行一个必要的匿名引用,这个是要注意的。