За годы использования C # / .NET для множества собственных проектов у нас была одна библиотека, органически растущая в одну огромную пачку вещей. Он называется «Утил», и я уверен, что многие из вас видели одного из этих зверей в своей карьере.
Многие части этой библиотеки очень автономны и могут быть разделены на отдельные проекты (которые мы хотели бы с открытым исходным кодом). Но есть одна важная проблема, которую необходимо решить, прежде чем их можно будет выпускать в виде отдельных библиотек. По сути, существует множество случаев, которые я мог бы назвать «необязательными зависимостями» между этими библиотеками.
Чтобы лучше это объяснить, рассмотрим некоторые из модулей, которые являются хорошими кандидатами на то, чтобы стать автономными библиотеками. CommandLineParser
для анализа командных строк. XmlClassify
для сериализации классов в XML. PostBuildCheck
выполняет проверку скомпилированной сборки и сообщает об ошибке компиляции, если она не удалась. ConsoleColoredString
библиотека для цветных строковых литералов Lingo
для перевода пользовательских интерфейсов.
Каждая из этих библиотек может использоваться полностью автономно, но если они используются вместе, то есть полезные дополнительные функции, которые необходимо иметь. Например, оба CommandLineParser
и XmlClassify
выставляют функциональность проверки после сборки, которая требует PostBuildCheck
. Точно так же, CommandLineParser
опция позволяет предоставлять документацию опций с использованием цветных строковых литералов, требующих ConsoleColoredString
, и поддерживает переводимую документацию через Lingo
.
Таким образом, ключевое отличие заключается в том, что это дополнительные функции . Можно использовать синтаксический анализатор командной строки с простыми неокрашенными строками, не переводя документацию и не выполняя никаких проверок после сборки. Или можно сделать документацию переводимой, но все же неокрашенной. Или как цветной, так и переводимый. И т.п.
Просматривая эту библиотеку «Util», я вижу, что почти все потенциально разделяемые библиотеки имеют такие дополнительные функции, которые связывают их с другими библиотеками. Если бы я на самом деле требовал, чтобы эти библиотеки были зависимостями, тогда эта пачка вещей на самом деле совсем не распутана: вам все равно потребуются все библиотеки, если вы хотите использовать только одну.
Существуют ли установленные подходы к управлению такими необязательными зависимостями в .NET?
источник
Ответы:
Рефакторинг Медленно.
Ожидайте, что это займет некоторое время , и может произойти в течение нескольких итераций, прежде чем вы сможете полностью удалить свою сборку Utils .
Общий подход:
Сначала уделите немного времени и подумайте, как вы хотите, чтобы эти сборки утилит выглядели, когда вы закончите. Не беспокойтесь о существующем коде, подумайте о конечной цели. Например, вы можете захотеть иметь:
Создайте пустые проекты для каждого из этих проектов и создайте соответствующие ссылки на проекты (ссылки на пользовательский интерфейс Core, UI.WinForms ссылки на пользовательский интерфейс) и т. Д.
Переместите любой низко висящий фрукт (классы или методы, которые не страдают от проблем с зависимостями) из вашей сборки Utils в новые целевые сборки.
Получить копию NDepend и Мартина Фаулера рефакторинга , чтобы начать анализировать ваши Utils сборки , чтобы начать работу на более жесткие из них. Две техники, которые будут полезны:
Обработка дополнительных интерфейсов
Либо сборка ссылается на другую сборку, либо нет. Единственный другой способ использования функциональных возможностей в несвязанной сборке - через интерфейс, загруженный посредством отражения от общего класса. Недостатком этого является то, что ваша сборка ядра должна будет содержать интерфейсы для всех общих функций, но плюсом является то, что вы можете развертывать свои утилиты по мере необходимости без «пачки» DLL-файлов в зависимости от каждого сценария развертывания. Вот как я бы обработал этот случай, используя цветную строку в качестве примера:
Сначала определите общие интерфейсы в вашей базовой сборке:
Например,
IStringColorer
интерфейс будет выглядеть так:Затем внедрите интерфейс в сборку с функцией. Например,
StringColorer
класс будет выглядеть так:Создайте
PluginFinder
(или, возможно, InterfaceFinder - более подходящее имя в данном случае) класс, который может находить интерфейсы из файлов DLL в текущей папке. Вот упрощенный пример. По совету @ EdWoodcock (и я согласен), когда ваши проекты растут, я бы предложил использовать одну из доступных платформ Dependency Injection (на мой взгляд, Common Serivce Locator с Unity и Spring.NET ) для более надежной реализации с более продвинутыми «найди меня» эта функция "возможности, иначе известный как шаблон локатора службы . Вы можете изменить его в соответствии с вашими потребностями.Наконец, используйте эти интерфейсы в других ваших сборках, вызвав метод FindInterface. Вот пример
CommandLineParser
:}
НАИБОЛЕЕ ВАЖНО: Тестируйте, тестируйте, тестируйте между каждым изменением.
источник
Вы можете использовать интерфейсы, объявленные в дополнительной библиотеке.
Попробуйте разрешить контракт (класс через интерфейс), используя внедрение зависимостей (MEF, Unity и т. Д.). Если не найден, установите его для возврата нулевого экземпляра.
Затем проверьте, является ли экземпляр нулевым, и в этом случае вы не выполняете дополнительные функции.
Это особенно легко сделать с помощью MEF, поскольку это учебник для него.
Это позволит вам скомпилировать библиотеки за счет разделения их на n + 1 dll.
НТН.
источник
Я думал, что опубликую самый жизнеспособный вариант, который мы придумали до сих пор, чтобы увидеть, что мы думаем.
По сути, мы разделили бы каждый компонент в библиотеку с нулевыми ссылками; весь код, который требует ссылки, будет помещен в
#if/#endif
блок с соответствующим именем. Например, код вCommandLineParser
этом дескриптореConsoleColoredString
s будет помещен в#if HAS_CONSOLE_COLORED_STRING
.Любое решение, которое хочет включить только,
CommandLineParser
может легко сделать это, так как больше нет никаких зависимостей. Однако, если решение также включаетConsoleColoredString
проект, у программиста теперь есть возможность:CommandLineParser
наConsoleColoredString
HAS_CONSOLE_COLORED_STRING
определение вCommandLineParser
файл проекта.Это сделало бы соответствующую функциональность доступной.
Есть несколько проблем с этим:
Скорее не красиво, но все же, это самое близкое, что мы придумали.
Еще одна идея, которую мы рассмотрели, заключалась в том, чтобы использовать конфигурации проекта, а не требовать от пользователя редактирования файла проекта библиотеки. Но это абсолютно неосуществимо в VS2010, потому что он добавляет все конфигурации проекта в решение нежелательно .
источник
Я собираюсь порекомендовать книгу Разработка приложений Brownfield в .Net . Две непосредственно соответствующие главы - это 8 и 9. Глава 8 рассказывает о ретрансляции вашего приложения, а глава 9 рассказывает о приручении зависимостей, инверсии управления и влиянии, которое это оказывает на тестирование.
источник
Полное раскрытие, я парень из Java. Так что я понимаю, что вы, вероятно, не ищете технологии, о которых я здесь упомяну. Но проблемы одинаковы, поэтому, возможно, это укажет вам правильное направление.
В Java существует ряд систем сборки, которые поддерживают идею централизованного хранилища артефактов, в котором хранятся встроенные «артефакты» - насколько мне известно, это несколько аналогично GAC в .NET (пожалуйста, извините мое невежество, если это напряженная анология) но более того, потому что он используется для создания независимых повторяемых сборок в любой момент времени.
В любом случае, другой поддерживаемой функцией (например, в Maven) является идея НЕОБЯЗАТЕЛЬНОЙ зависимости, которая затем зависит от конкретных версий или диапазонов и потенциально исключает переходные зависимости. Это звучит для меня как то, что вы ищете, но я могу ошибаться. Взгляните на эту вводную страницу по управлению зависимостями от Maven с другом, который знает Java, и посмотрите, кажутся ли проблемы знакомыми. Это позволит вам построить ваше приложение и создать его с доступностью этих зависимостей или без нее.
Существуют также конструкции, если вам нужна действительно динамическая подключаемая архитектура; Одной из технологий, которая пытается решить эту форму разрешения зависимостей во время выполнения, является OSGI. Это двигатель системы плагинов Eclipse . Вы увидите, что он может поддерживать необязательные зависимости и минимальный / максимальный диапазон версий. Этот уровень модульности времени исполнения накладывает множество ограничений на вас и то, как вы развиваетесь. Большинство людей могут обойтись той степенью модульности, которую обеспечивает Maven.
Еще одна возможная идея, которую вы могли бы рассмотреть, которая может быть проще для вас реализовать, - это использовать стиль архитектуры Pipes and Filters. Это во многом то, что сделало UNIX такой давней, успешной экосистемой, которая выжила и развивалась в течение полувека. Взгляните на эту статью о трубах и фильтрах в .NET, чтобы узнать, как реализовать этот тип шаблона в вашей среде.
источник
Возможно, книга Джона Лакоса «Проектирование крупномасштабного программного обеспечения на C ++» полезна (конечно, C # и C ++ или не одно и то же, но вы можете извлечь полезные приемы из этой книги).
По сути, перефакторинг и перемещение функций, использующих две или более библиотек, в отдельный компонент, который зависит от этих библиотек. При необходимости используйте такие методы, как непрозрачные типы и т. Д.
источник