Регистрация пакетов в Go без циклической зависимости

У меня есть центральный пакет, который предоставляет несколько интерфейсов, от которых зависят другие пакеты (назовем один Client). Эти другие пакеты предоставляют несколько реализаций этих первых интерфейсов (UDPClient, TCPClient). Я создаю экземпляр Client, вызывая NewClient в центральном пакете, и он выбирает и вызывает соответствующую реализацию клиента из одного из зависимых пакетов.

Это разваливается, когда я хочу рассказать центральный пакет об этих других пакетах, поэтому он знает, какие клиенты он может создать. Эти зависимые клиентские реализации также импортируют центральный пакет, создавая циклическую зависимость, которую Go не позволяет.

Какой лучший способ продвижения вперед? Я бы предпочел не сбрасывать все эти реализации в одном пакете, и создание отдельного пакета реестра кажется излишним. В настоящее время у меня есть каждый регистр реализации с центральным пакетом, но это требует, чтобы пользователь знал, чтобы импортировать каждую реализацию в каждом отдельном двоичном файле, использующем клиента.

import (
    _ udpclient
    _ tcpclient
    client
)

Ответы

Ответ 1

Стандартная библиотека решает эту проблему несколькими способами:

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) Предложение 3-го решения: "Пользовательский" реестр

У вас все еще может быть удобство "центрального" реестра с интерфейсом и реализациями, разделенными за счет "автоматической расширяемости".

Идея заключается в том, что у вас есть интерфейс в пакете pi. У вас есть реализации в пакете pa, pb и т.д.

И вы создадите пакет pf, который будет иметь нужные вам методы "factory", например. pf.NewClient(). Пакет pf может ссылаться на пакеты pa, pb, pi без создания циклической зависимости.

Ответ 2

Эти зависимые реализации клиента также импортируют центральный пакет

Они должны полагаться на другой пакет, определяющий интерфейсы, на которые им нужно полагаться (и которые реализуются первым центральным пакетом).

Обычно это как цикл импорта прерывается (и/или с помощью инверсия зависимостей).

У вас есть больше опций, описанных в разделе Циклические зависимости и интерфейсы в Голанге.

go list -f также может помочь визуализировать эти циклы импорта.