使用Go注册软件包时无循环依赖 [英] Registering packages in Go without cyclic dependency

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

问题描述

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

当我想告诉中央软件包关于其他软件包的情况时,这种情况就会崩溃,因此它知道它可以创建哪些客户端。那些从属的客户端实现也导入了中心包,创建了Go不允许的循环依赖。



最好的方法是什么?我不想将所有这些实现混合在一个包中,并且创建一个单独的注册表包似乎过度。目前我有每个实现注册自己的中央包,但这需要用户知道导入每个实现在每个单独的二进制使用客户端。

  import(
_ udpclient
_ tcpclient
client


解决方案

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

中央注册表



这是不同的散列算法的例子。 crypto 软件包只是定义了 Hash 界面(类型及其方法)。具体实现在不同的包中(实际上是子文件夹,但不一定是),例如 crypto / md5 crypto /当你需要一个hasher时,你明确地指出你想要哪一个并且实例化那个,比如:sha256

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

这是最简单的解决方案,它也给了你很好的分离: hash 包不需要知道或担心实现。



如果您知道或者您可以决定您希望实现哪种实现,则这是首选解决方案。 b
$ b

2)使用中央注册表



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

这是 image 软件包。该软件包定义了 Image 界面和它的几个实现。不同的图像格式在不同的包中定义,例如 image / gif image / jpeg image / png



image 包有一个 Decode() 函数解码并返回一个 图片 io.Reader 。通常不知道什么类型的图像来自读者,所以你不能使用特定图像格式的解码器算法。



在这种情况下,如果我们想要图像解码机制是可扩展的,注册是不可避免的。执行此操作最简单的方法是在包 init()函数中,这是通过在导入时指定包名的空白标识符触发的。



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




所以最好的方法是什么?



取决于什么你的要求是。如果您知道或者您可以决定需要哪种实施方式,请使用#1。如果你不能决定,或者你不知道,而且你需要扩展性,那就去#2。



...或者用#3表示。 3)建议第三个解决方案:自定义注册表

您仍然可以享受中央注册表中的接口和实现以自动扩展性为代价分开。



这个想法是,你在 pi 。你已经在 pa pb 等等的实现中。



然后创建一个包含 pf 的包,它将包含所需的工厂方法,例如 pf.NewClient() pf 包可以引用包 pa pb pi 而不创建循环依赖。


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.

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:

1) Without a "Central" Registry

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()

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.

2) With a "Central" Registry

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

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.

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.

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.

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().


So the best way?

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.

...Or go with #3 presented below.

3) Proposing a 3rd Solution: "Custom" Registry

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

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

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天全站免登陆