24

我有一个中央包,它提供了其他包所依赖的几个接口(让我们称之为一个Client)。那些其他包提供了这些第一个接口(UDPClient, TCPClient)的几种实现。Client我通过调用NewClient中央包来实例化 a ,它从依赖包之一中选择并调用适当的客户端实现。

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

最好的前进方式是什么?我不希望将所有这些实现混搭在一个包中,并且创建一个单独的注册表包似乎有点过头了。目前我让每个实现都将自己注册到中央包,但这要求用户知道在每个使用客户端的单独二进制文件中导入每个实现。

import (
    _ udpclient
    _ tcpclient
    client
)
4

2 回答 2

34

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

1) 没有“中央”注册表

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

当您需要一个“哈希器”时,您明确说明您想要哪个并实例化那个,例如

h1 := md5.New()
h2 := sha256.New()

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

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

2) 使用“中央”注册表

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

一个例子就是image包。该包定义了Image接口和它的几个实现。不同的图像格式定义在不同的包中,例如image/gif,image/jpegimage/png.

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

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

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


那么最好的方法呢?

取决于你的要求是什么。如果您知道或者您可以决定您需要哪种实现,请选择 #1。如果您无法决定或者您不知道并且您需要可扩展性,请选择 #2。

...或者选择下面介绍的#3。

3) 提出第三种解决方案:“自定义”注册表

您仍然可以享受“中央”注册表的便利,该注册表将接口和实现分开,但需要付出“自动扩展性”的代价。

这个想法是你在 package 中有接口pi。您在 package 等中有pa实现pb

并且您创建了一个包含pf您想要的“工厂”方法的包,例如pf.NewClient(). pfpackage 可以引用 packages , papb而不pi创建循环依赖。

于 2015-03-26T07:19:02.907 回答
4

那些依赖的客户端实现也导入了中心包

他们应该依赖另一个包来定义他们需要依赖的接口(并且由第一个中央包实现)。

这通常是如何破坏导入周期(和/或使用依赖反转)。

您可以在“ Golang 中的循环依赖项和接口”中描述更多选项。

go list -f还可以帮助可视化这些导入周期。

于 2015-03-26T06:44:48.520 回答