Registro de paquetes en Go sin dependencia cíclica.
packaging cyclic-dependency (2)
Las implementaciones de clientes dependientes también importan el paquete central.
Deben confiar en otro paquete que defina las interfaces en las que deben confiar (y que se implementan con el primer paquete central).
Esto suele ser cómo se rompe un ciclo de importación (y / o mediante la inversión de dependencia ).
Tiene más opciones descritas en " Dependencias e interfaces cíclicas en Golang ".
go list -f
también puede ayudar a visualizar esos ciclos de importación.
Tengo un paquete central que proporciona varias interfaces de las que dependen otros paquetes (llamemos a un Client
). Esos otros paquetes, proporcionan varias implementaciones de esas primeras interfaces ( UDPClient
, TCPClient
). Hago una instancia de un Client
llamando a NewClient
en el paquete central, y selecciona e invoca la implementación de cliente apropiada de uno de los paquetes dependientes.
Esto se desmorona cuando quiero decirle al paquete central sobre esos otros paquetes, para que sepa qué clientes pueden crear. Las implementaciones de clientes dependientes también importan el paquete central, creando una dependencia cíclica que Go no permite.
¿Cuál es la mejor manera de avanzar? Preferiría no combinar todas esas implementaciones en un solo paquete, y crear un paquete de registro separado parece excesivo. Actualmente, cada implementación se registra con el paquete central, pero esto requiere que el usuario sepa importar cada implementación en cada binario separado que haga uso del cliente.
import (
_ udpclient
_ tcpclient
client
)
La biblioteca estándar resuelve este problema de múltiples maneras:
1) Sin un registro "central"
Ejemplo de esto son los diferentes algoritmos hash. El paquete crypto
simplemente define la interfaz Hash
(el tipo y sus métodos). Las implementaciones concretas están en diferentes paquetes (en realidad, subcarpetas, pero no es necesario que estén), por ejemplo, crypto/md5
y crypto/sha256
.
Cuando necesita un "hasher", declara explícitamente cuál desea y crea una instancia de éste, por ejemplo,
h1 := md5.New()
h2 := sha256.New()
Esta es la solución más simple y también le brinda una buena separación: el paquete hash
no tiene que saber ni preocuparse por las implementaciones.
Esta es la solución preferida si sabe o puede decidir qué implementación desea antes.
2) Con un registro "central"
Esta es básicamente tu solución propuesta. Las implementaciones tienen que registrarse de alguna manera (generalmente en una función de paquete init()
).
Un ejemplo de esto es el paquete de image
. El paquete define la interfaz de la Image
y varias de sus implementaciones. Los diferentes formatos de imagen se definen en diferentes paquetes, como image/gif
, image/jpeg
e image/png
.
El paquete de image
tiene una función image que decodifica y devuelve una Image
del io.Reader
especificado. A menudo no se sabe qué tipo de imagen proviene del lector y, por lo tanto, no se puede usar el algoritmo de decodificación de un formato de imagen específico.
En este caso, si queremos que el mecanismo de decodificación de la imagen sea extensible, un registro es inevitable. Lo más limpio para hacer esto es en las funciones del paquete init()
que se activan al especificar el identificador en blanco para el nombre del paquete al importar.
Tenga en cuenta que esta solución también le brinda la posibilidad de usar una implementación específica para decodificar una imagen, las implementaciones concretas también brindan la función Decode()
, por ejemplo png.Decode()
.
Entonces, ¿la mejor manera?
Depende de cuales sean tus requerimientos. Si sabe o puede decidir qué implementación necesita, vaya con # 1. Si no puede decidir o no sabe y necesita extensibilidad, vaya con el # 2.
... O ir con el # 3 presentado abajo.
3) Proponer una tercera solución: Registro "personalizado"
Aún puede tener la conveniencia del registro "central" con interfaz e implementaciones separadas con el costo de la "auto extensibilidad".
La idea es que tengas la interfaz en el paquete pi
. Tienes implementaciones en el paquete pa
, pb
etc.
Y creas un paquete pf
que tendrá los métodos "de fábrica" que deseas, por ejemplo, pf.NewClient()
. El paquete pf
puede referirse a los paquetes pa
, pb
, pi
sin crear una dependencia circular.