Регистрация пакетов в Go без циклической зависимости
У меня есть центральный пакет, который предоставляет несколько интерфейсов, от которых зависят другие пакеты (давайте назовем один Client
). Эти другие пакеты предоставляют несколько реализаций этих первых интерфейсов (UDPClient
, TCPClient
). Я создаю экземпляр Client
позвонив NewClient
в центральном пакете, и он выбирает и вызывает соответствующую реализацию клиента из одного из зависимых пакетов.
Это разваливается, когда я хочу рассказать центральному пакету об этих других пакетах, чтобы он знал, каких клиентов он может создать. Эти реализации зависимых клиентов также импортируют центральный пакет, создавая циклическую зависимость, которую Go не позволяет.
Какой лучший путь вперед? Я бы предпочел не смешивать все эти реализации в одном пакете, и создание отдельного пакета реестра кажется излишним. В настоящее время каждая реализация регистрируется в центральном пакете, но для этого требуется, чтобы пользователь знал, что нужно импортировать каждую реализацию в каждый отдельный двоичный файл, в котором используется клиент.
import (
_ udpclient
_ tcpclient
client
)
2 ответа
Стандартная библиотека решает эту проблему несколькими способами:
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) Предложение третьего решения: "Пользовательский" реестр
Вы можете по-прежнему иметь удобство "центрального" реестра с интерфейсом и реализациями, разделенными за счет "авторасширяемости".
Идея в том, что у вас есть интерфейс в упаковке pi
, У вас есть реализации в пакете pa
, pb
и т.п.
И вы создаете пакет pf
которые будут иметь "фабричные" методы, которые вы хотите, например, pf.NewClient()
, pf
пакет может ссылаться на пакеты pa
, pb
, pi
без создания круговой зависимости.
Эти зависимые клиентские реализации также импортируют центральный пакет
Они должны полагаться на другой пакет, определяющий интерфейсы, на которые они должны полагаться (и которые реализуются первым центральным пакетом).
Это обычно, как цикл импорта прерывается (и / или с использованием инверсии зависимостей).
У вас есть дополнительные параметры, описанные в разделе" Циклические зависимости и интерфейсы в Golang".
go list -f
может также помочь визуализировать эти циклы импорта.