标准库以多种方式解决了这个问题:
1) 没有“中央”注册表
这方面的例子是不同的哈希算法。该crypto包只定义了Hash接口(类型及其方法)。具体实现位于不同的包中(实际上是子文件夹,但不需要),例如crypto/md5和crypto/sha256.
当您需要一个“哈希器”时,您明确说明您想要哪个并实例化那个,例如
h1 := md5.New()
h2 := sha256.New()
这是最简单的解决方案,它也为您提供了很好的分离:hash包不必知道或担心实现。
如果您知道或者您可以决定之前想要的实现,这是首选的解决方案。
2) 使用“中央”注册表
这基本上是您提出的解决方案。实现必须以某种方式注册自己(通常在包init()函数中)。
一个例子就是image包。该包定义了Image接口和它的几个实现。不同的图像格式定义在不同的包中,例如image/gif,image/jpeg和image/png.
该image包有一个Decode()函数,它解码并Image从指定的io.Reader. 通常不知道来自阅读器的图像类型,因此您不能使用特定图像格式的解码器算法。
在这种情况下,如果我们希望图像解码机制是可扩展的,注册是不可避免的。最干净的做法是在包init()函数中,它通过在导入时为包名称指定空白标识符来触发。
请注意,此解决方案还为您提供了使用特定实现来解码图像的可能性,具体实现也提供了Decode()功能,例如png.Decode().
那么最好的方法呢?
取决于你的要求是什么。如果您知道或者您可以决定您需要哪种实现,请选择 #1。如果您无法决定或者您不知道并且您需要可扩展性,请选择 #2。
...或者选择下面介绍的#3。
3) 提出第三种解决方案:“自定义”注册表
您仍然可以享受“中央”注册表的便利,该注册表将接口和实现分开,但需要付出“自动扩展性”的代价。
这个想法是你在 package 中有接口pi。您在 package 等中有pa实现pb。
并且您创建了一个包含pf您想要的“工厂”方法的包,例如pf.NewClient(). pfpackage 可以引用 packages , pa,pb而不pi创建循环依赖。