在Go中注册包而没有循环依赖

Mat*_*ner 20 packaging go cyclic-dependency

我有一个中央包,提供其他包依赖的几个接口(让我们调用一个Client).那些其他包提供了那些第一个接口(UDPClient,TCPClient)的几个实现.我Client通过调用NewClient中央包来实例化,并从一个依赖包中选择并调用相应的客户端实现.

当我想告诉中央包关于那些其他包时,它就会崩溃,所以它知道它可以创建什么客户端.这些依赖的客户端实现也导入中央包,创建Go不允许的循环依赖.

前进的最佳方式是什么?我不想将所有这些实现混合在一个包中,并且创建一个单独的注册表包似乎有点过分.目前,我将每个实现注册本身与中央包一起注册,但这要求用户知道在每个使用客户端的单独二进制文件中导入每个实现.

import (
    _ udpclient
    _ tcpclient
    client
)
Run Code Online (Sandbox Code Playgroud)

icz*_*cza 27

标准库以多种方式解决了这个问题:

1)没有"中央"注册表

这样的示例是不同的散列算法.该crypto包只定义了Hash接口(类型及其方法).具体实现在不同的包中(实际上是子文件夹但不需要),例如crypto/md5crypto/sha256.

当你需要一个"hasher"时,你明确说明你想要哪一个并实例化那个,例如

h1 := md5.New()
h2 := sha256.New()
Run Code Online (Sandbox Code Playgroud)

这是最简单的解决方案,它也为您提供了良好的分离:hash包不必知道或担心实现.

如果您知道或者您可以决定之前想要的实现,那么这是首选解决方案.

2)使用"中央"注册表

这基本上是您提出的解决方案.实现必须以某种方式注册自己(通常在包init()函数中).

这方面的一个例子是image包.该包定义了Image接口及其几个实现.不同的图像格式在不同的包中定义,例如image/gif,image/jpegimage/png.

image包具有一个Decode()函数,该函数解码并返回Image指定的io.Reader.通常不知道什么类型的图像来自阅读器,因此您不能使用特定图像格式的解码器算法.

在这种情况下,如果我们希望图像解码机制是可扩展的,则注册是不可避免的.最干净的是在包init()函数中,通过在导入时指定包名称的空白标识符来触发.

请注意,此解决方案还使您可以使用特定实现来解码图像,具体实现也提供了该Decode()功能png.Decode().


那么最好的方法呢?

取决于您的要求.如果您知道或者您可以决定需要哪种实施方式,请使用#1.如果您无法决定或者您不知道并且需要可扩展性,请使用#2.

...或者使用下面的#3.

3)提出第三个解决方案:"自定义"注册表

您仍然可以方便地使用"中央"注册表,其界面和实现以"自动扩展性"为代价分开.

我们的想法是你有接口包pi.你必须在包的实现pa,pb等等.

然后你创建一个包含pf你想要的"工厂"方法的包,例如pf.NewClient().该pf包可以指包pa,pb,pi而不创建循环依赖关系.