A lo largo de los años de usar C # / .NET para un montón de proyectos internos, hemos tenido una biblioteca que creció orgánicamente en un gran fajo de cosas. Se llama "Util", y estoy seguro de que muchos de ustedes han visto una de estas bestias en sus carreras.
Muchas partes de esta biblioteca son muy independientes y podrían dividirse en proyectos separados (que nos gustaría abrir en código fuente). Pero hay un problema importante que debe resolverse antes de que puedan lanzarse como bibliotecas separadas. Básicamente, hay muchos casos de lo que podría llamar "dependencias opcionales" entre estas bibliotecas.
Para explicar esto mejor, considere algunos de los módulos que son buenos candidatos para convertirse en bibliotecas independientes. CommandLineParser
es para analizar líneas de comando. XmlClassify
es para serializar clases a XML. PostBuildCheck
realiza comprobaciones en el ensamblado compilado e informa un error de compilación si fallan. ConsoleColoredString
es una biblioteca para literales de cadena de colores. Lingo
es para traducir interfaces de usuario.
Cada una de esas bibliotecas se puede usar de manera completamente independiente, pero si se usan juntas, entonces hay características adicionales útiles que se pueden tener. Por ejemplo, ambos CommandLineParser
y XmlClassify
exponen la funcionalidad de comprobación posterior a la compilación, que requiere PostBuildCheck
. Del mismo modo, CommandLineParser
permite que se proporcione la documentación de la opción utilizando los literales de cadena de color, lo que requiere ConsoleColoredString
, y admite documentación traducible a través de Lingo
.
Entonces, la distinción clave es que estas son características opcionales . Se puede usar un analizador de línea de comandos con cadenas simples y sin color, sin traducir la documentación ni realizar ninguna comprobación posterior a la compilación. O uno podría hacer que la documentación sea traducible pero aún sin color. O tanto de color como traducible. Etc.
Al mirar a través de esta biblioteca "Util", veo que casi todas las bibliotecas potencialmente separables tienen características opcionales que las vinculan a otras bibliotecas. Si realmente requiriera esas bibliotecas como dependencias, este fajo de cosas no está realmente desenredado en absoluto: básicamente necesitaría todas las bibliotecas si desea usar solo una.
¿Existen enfoques establecidos para administrar tales dependencias opcionales en .NET?
fuente
Respuestas:
Refactorizar lentamente.
Espere que esto tarde un tiempo en completarse , y puede ocurrir en varias iteraciones antes de que pueda eliminar por completo su ensamblaje de Utils .
Enfoque global:
Primero tómese un tiempo y piense cómo quiere que se vean estos conjuntos de utilidades cuando haya terminado. No se preocupe demasiado por su código existente, piense en el objetivo final. Por ejemplo, es posible que desee tener:
Cree proyectos vacíos para cada uno de estos proyectos y cree referencias de proyecto apropiadas (referencias de UI Core, UI.WinForms referencias UI), etc.
Mueva cualquiera de las frutas bajas (clases o métodos que no sufren los problemas de dependencia) de su ensamblaje de Utils a los nuevos ensamblajes de destino.
Obtenga una copia de NDepend y Martin Fowler's Refactoring para comenzar a analizar su ensamblaje de Utils para comenzar a trabajar en los más difíciles. Dos técnicas que serán útiles:
Manejo de interfaces opcionales
O un ensamblaje hace referencia a otro ensamblaje, o no lo hace. La única otra forma de usar la funcionalidad en un ensamblaje no vinculado es a través de una interfaz cargada mediante la reflexión de una clase común. La desventaja de esto es que su ensamblaje central necesitará contener interfaces para todas las características compartidas, pero la ventaja es que puede implementar sus utilidades según sea necesario sin el "fajo" de archivos DLL dependiendo de cada escenario de implementación. Así es como manejaría este caso, usando la cadena de color como ejemplo:
Primero, defina las interfaces comunes en su ensamblaje central:
Por ejemplo, la
IStringColorer
interfaz se vería así:Luego, implemente la interfaz en el ensamblaje con la función. Por ejemplo, la
StringColorer
clase se vería así:Crear un
PluginFinder
clase (o quizás InterfaceFinder es un nombre mejor en este caso) que puede encontrar interfaces de archivos DLL en la carpeta actual. Aquí hay un ejemplo simplista. Según el consejo de @ EdWoodcock (y estoy de acuerdo), cuando sus proyectos crezcan, sugeriría utilizar uno de los marcos de inyección de dependencia disponibles ( Common Serivce Locator con Unity y Spring.NET ) para una implementación más robusta con más "encuéntreme" esa característica "capacidades, también conocida como el patrón de localización de servicios . Puede modificarlo para satisfacer sus necesidades.Por último, use estas interfaces en sus otros ensamblados llamando al método FindInterface. Aquí hay un ejemplo de
CommandLineParser
:}
MÁS IMPORTANTE: Prueba, prueba, prueba entre cada cambio.
fuente
Puede utilizar las interfaces declaradas en una biblioteca adicional.
Intente resolver un contrato (clase a través de la interfaz) utilizando una inyección de dependencia (MEF, Unity, etc.). Si no se encuentra, configúrelo para que devuelva una instancia nula.
Luego verifique si la instancia es nula, en cuyo caso no realiza las funcionalidades adicionales.
Esto es especialmente fácil de hacer con MEF, ya que es el uso del libro de texto para ello.
Le permitiría compilar las bibliotecas, a costa de dividirlas en n + 1 dlls.
HTH
fuente
Pensé en publicar la opción más viable que se nos haya ocurrido hasta ahora, para ver cuáles son los pensamientos.
Básicamente, separaríamos cada componente en una biblioteca con cero referencias; Todo el código que requiere una referencia se colocará en un
#if/#endif
bloque con el nombre apropiado. Por ejemplo, el códigoCommandLineParser
que manejaConsoleColoredString
s se colocaría en#if HAS_CONSOLE_COLORED_STRING
.Cualquier solución que desee incluir solo
CommandLineParser
puede hacerlo fácilmente, ya que no hay más dependencias. Sin embargo, si la solución también incluye elConsoleColoredString
proyecto, el programador ahora tiene la opción de:CommandLineParser
aConsoleColoredString
HAS_CONSOLE_COLORED_STRING
definición alCommandLineParser
archivo del proyecto.Esto haría que la funcionalidad relevante estuviera disponible.
Hay varios problemas con esto:
Más bien poco bonito, pero aún así, esto es lo más cercano que hemos encontrado.
Otra idea que consideramos fue usar configuraciones de proyecto en lugar de requerir que el usuario edite el archivo de proyecto de la biblioteca. Pero esto es absolutamente inviable en VS2010 porque agrega todas las configuraciones de proyecto a la solución de forma no deseada .
fuente
Voy a recomendar el libro Brownfield Application Development en .Net . Dos capítulos directamente relevantes son 8 y 9. El capítulo 8 habla sobre la retransmisión de su aplicación, mientras que el capítulo 9 habla sobre la dependencia de la domesticación, la inversión del control y el impacto que esto tiene en las pruebas.
fuente
Revelación completa, soy un chico de Java. Así que entiendo que probablemente no estés buscando las tecnologías que mencionaré aquí. Pero los problemas son los mismos, por lo que tal vez te indique la dirección correcta.
En Java, hay una serie de sistemas de compilación que respaldan la idea de un repositorio de artefactos centralizado que alberga "artefactos" construidos, que yo sepa, esto es algo análogo al GAC en .NET (perdone mi ignorancia si es una anaología tensa) pero más que eso porque se usa para producir compilaciones repetibles independientes en cualquier momento.
De todos modos, otra característica que es compatible (en Maven, por ejemplo) es la idea de una dependencia OPCIONAL, que luego depende de versiones o rangos específicos y potencialmente excluye las dependencias transitivas. Esto me parece lo que estás buscando, pero podría estar equivocado. Eche un vistazo a esta página de introducción sobre la gestión de dependencias de Maven con un amigo que conoce Java y vea si los problemas le resultan familiares. Esto le permitirá construir su aplicación y construirla con o sin tener estas dependencias disponibles.
También hay construcciones si necesita una arquitectura verdaderamente dinámica y conectable; Una tecnología que intenta abordar esta forma de resolución de dependencia de tiempo de ejecución es OSGI. Este es el motor detrás del sistema de complementos de Eclipse . Verá que puede admitir dependencias opcionales y un rango de versión mínimo / máximo. Este nivel de modularidad en tiempo de ejecución le impone una buena cantidad de restricciones y cómo se desarrolla. La mayoría de las personas pueden sobrevivir con el grado de modularidad que ofrece Maven.
Otra posible idea que podría considerar que podría ser un orden de magnitud más simple de implementar para usted es utilizar un estilo de arquitectura de Tuberías y Filtros. Esto es en gran medida lo que ha convertido a UNIX en un ecosistema exitoso y de larga data que ha sobrevivido y evolucionado durante medio siglo. Eche un vistazo a este artículo sobre Tuberías y filtros en .NET para obtener algunas ideas sobre cómo implementar este tipo de patrón en su marco.
fuente
Quizás el libro "Diseño de software C ++ a gran escala" de John Lakos sea útil (por supuesto, C # y C ++ o no es lo mismo, pero puede extraer técnicas útiles del libro).
Básicamente, vuelva a factorizar y mueva la funcionalidad que usa dos o más bibliotecas a un componente separado que depende de estas bibliotecas. Si es necesario, utilice técnicas como tipos opacos, etc.
fuente