在 Go 中注册没有循环依赖的包 [英] Registering packages in Go without cyclic dependency

查看:48
本文介绍了在 Go 中注册没有循环依赖的包的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a central package that provides several interfaces that other packages are dependent on (let us call one Client). Those other packages, provide several implementations of those first interfaces (UDPClient, TCPClient). I instantiate a Client by calling NewClient in the central package, and it selects and invokes the appropriate client implementation from one of the dependent packages.

当我想告诉中央包有关其他包的信息时,这会崩溃,因此它知道它可以创建哪些客户端.那些依赖的客户端实现也会导入中央包,从而创建 Go 不允许的循环依赖.

This falls apart when I want to tell the central package about those other packages, so it knows what clients it can create. Those dependent client implementations also import the central package, creating a cyclic dependency which Go does not allow.

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

What's the best way forward? I'd prefer not to mash all those implementations in a single package, and creating a separate registry package seems overkill. Currently I have each implementation register itself with the central package, but this requires that the user knows to import every implementation in every separate binary that makes use of client.

import (
    _ udpclient
    _ tcpclient
    client
)

推荐答案

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

The standard library solves this problem in multiple ways:

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

Example of this is the different hash algorithms. The crypto package just defines the Hash interface (the type and its methods). Concrete implementations are in different packages (actually subfolders but doesn't need to be) for example crypto/md5 and crypto/sha256.

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

When you need a "hasher", you explicitly state which one you want and instantiate that one, e.g.

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

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

This is the simplest solution and it also gives you good separation: the hash package does not have to know or worry about implementations.

如果您知道或者您可以决定要事先采用哪种实现方式,这是首选解决方案.

This is the preferred solution if you know or you can decide which implementation you want prior.

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

This is basically your proposed solution. Implementations have to register themselves in some way (usually in a package init() function).

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

An example of this is the image package. The package defines the Image interface and several of its implementations. Different image formats are defined in different packages such as image/gif, image/jpeg and image/png.

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

The image package has a Decode() function which decodes and returns an Image from the specified io.Reader. Often it is unknown what type of image comes from the reader and so you can't use the decoder algorithm of a specific image format.

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

In this case if we want the image decoding mechanism to be extensible, a registration is unavoidable. The cleanest to do this is in package init() functions which is triggered by specifying the blank identifier for the package name when importing.

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

Note that this solution also gives you the possibility to use a specific implementation to decode an image, the concrete implementations also provide the Decode() function, for example png.Decode().

所以最好的方法是?

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

Depends on what your requirements are. If you know or you can decide which implementation you need, go with #1. If you can't decide or you don't know and you need extensibility, go with #2.

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

...Or go with #3 presented below.

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

You can still have the convenience of the "central" registry with interface and implementations separated with the expense of "auto-extensibility".

这个想法是你在包 pi 中有接口.您在 papb 等包中有实现.

The idea is that you have the interface in package pi. You have implementations in package pa, pb etc.

并且您创建了一个包 pf,它将包含您想要的工厂"方法,例如pf.NewClient().pf 包可以引用包papbpi,而无需创建循环依赖.

And you create a package pf which will have the "factory" methods you want, e.g. pf.NewClient(). The pf package can refer to packages pa, pb, pi without creating a circular dependency.

这篇关于在 Go 中注册没有循环依赖的包的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆