Какова лучшая стратегия для внедрения зависимостей пользовательского ввода?
Я использовал достаточное количество инъекций зависимостей, но я хотел бы получить информацию о том, как обрабатывать информацию от пользователя во время выполнения.
У меня есть класс, который подключается к COM-порту. Я разрешаю пользователю выбрать номер com-порта. Прямо сейчас у меня есть этот параметр com-порта в качестве аргумента конструктора. Причина в том, что класс не может функционировать без этой информации, и это зависит от конкретной реализации (для фиктивной версии этого класса не нужен com-порт).
Альтернативой является использование метода "Start", который принимает com-порт, или свойство, которое устанавливает com-порт. Это делает его очень совместимым с контейнером IoC, но это не обязательно имеет смысл с точки зрения класса.
Кажется, что логический маршрут конфликтует с дизайном внедрения зависимостей, но это потому, что мой пользовательский интерфейс получает информацию для определенного типа класса.
Другие альтернативы включают использование контейнера IoC, который позволяет мне передавать дополнительные параметры конструктора, или просто создание классов, которые мне нужны, на верхнем уровне без использования внедрения зависимостей.
Существует ли общепринятый стандартный шаблон для этого типа проблемы?
2 ответа
Есть два маршрута, которые вы можете выбрать, в зависимости от ваших потребностей.
1. Подключите интерфейс непосредственно к вашим конкретным классам
Это самый простой вариант, но во многих случаях вполне приемлемый. Хотя у вас может быть Доменная модель с большим количеством интерфейсов и использованием DI, пользовательский интерфейс представляет собой корень композиции графов объектов, и вы можете просто подключить здесь свой конкретный класс, включая требуемый параметр номера порта.
Плюс в том, что этот подход прост и легок для понимания и реализации.
Недостатком является то, что вы получаете меньше гибкости. Вы не сможете произвольно заменить одну реализацию другой (но опять же, вам может не понадобиться такая гибкость).
Даже если пользовательский интерфейс привязан к конкретной реализации, это не означает, что модель домена сама по себе не будет использоваться в других приложениях.
2. Добавить абстрактную фабрику
Другой вариант - добавить еще один слой косвенности. Вместо того чтобы ваш пользовательский интерфейс создавал класс напрямую, он мог бы использовать Abstract Factory для создания экземпляра.
Заводской метод Create может принимать номер порта в качестве входного, поэтому эта абстракция лучше всего относится к подуровню пользовательского интерфейса.
public abstract class MyFactory
{
public abstract IMyInterface Create(int portNumber);
}
Затем вы могли бы связать свой DI-контейнер с реализацией этой фабрики, которая использует номер порта и передает его в качестве аргумента конструктора вашей реальной реализации. Другие реализации фабрики могут просто игнорировать параметр.
Преимущество этого подхода состоит в том, что вы не загрязняете свой API (или ваши конкретные реализации), и у вас все еще есть гибкость, которую дает вам программирование интерфейсов.
Недостатком является то, что он добавляет еще один слой косвенности.
Большинство IoC-контейнеров имеют какую-то форму Constructor Injection, которая позволила бы вашему IoC-контейнеру передавать поддельный COM-порт в ваш класс для модульного тестирования. Это кажется самым чистым решением.
Я бы не стал добавлять метод "Пуск" и т. Д. Его гораздо лучше (когда это возможно) всегда поддерживать ваши классы в допустимом состоянии, а переключение на конструктор без параметров с помощью метода запуска делает ваш класс недопустимым между этими вызовами. Выполнение этого, чтобы включить тестирование, просто делает ваш класс более сложным для тестирования (что должно сделать его лучше).