Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Контейнер зависимостей: как создать экземпляры объекта

Автор вопроса: BuddaОтветов: 1

Я использую Unity как движок впрыска зависимостей. Он содержит интерфейсы / классы для классов типа "репозиторий" и "менеджер". Эти репозитории отвечают за получение / сохранение / обновление данных из / в БД, а менеджеры "знают" об отношениях объектов с другими компонентами / классами.

Реализована фабрика для получения экземпляра контейнера зависимостей (1 экземпляр на запрос). Когда создается какой-либо класс репозитория / менеджера, он получает контейнер зависимостей в качестве параметра конструктора и может при необходимости создавать все другие менеджеры / репозитории.

Есть 2 способа создания бизнес-сущностей:

  1. Когда данные извлекаются из репозитория БД, создаются экземпляры объектов и инициализируются соответствующими данными БД, среди прочего инициализируется поле "Контейнер".
  2. Когда объект создается в коде, который будет помещен в БД, среди прочего, он принимает параметр "IUnityContainer".

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

Постановка проблемы: на прошлой неделе я прочитал несколько вопросов / ответов по SO, в которых говорится примерно следующее:

  1. экземпляры объектов не должны иметь доступа к контейнеру зависимостей;
  2. вместо этого они должны получить все необходимые интерфейсы в конструкторе.

Думаю, то же самое применимо и к классам менеджера / репозитория: я не должен передавать экземпляр контейнера их конструктору, вместо этого я должен помещать интерфейсы к другим необходимым компонентам.

Мне кажется разумным, но:

  • Мне потребуется, чтобы любой объект предоставлял ВСЕ интерфейсы, которые ему нужны (обычно каждому менеджеру / репозиторию / сущности требуется 3-5 из них);

  • Очень много случаев, когда требуется всего 1-2 интерфейса (поэтому я не хочу создавать много других);

Вопрос 1: действительно ли это хороший способ "скрыть" контейнер? Почему? (на самом деле, мне кажется, я знаю почему, но если у вас есть хороший ответ, сообщите, пожалуйста).

Вопрос 2. Как лучше всего решить эту проблему?

Вопрос 3: в моей конкретной реализации, когда мои объекты извлекаются из БД (я использую Linq2Sql и думаю о переключении на EF), я не могу создавать объекты с помощью DependencyContainer, я должен сделать это сам (потому что объекты создаются с использованием выражения, которое выполняется на сайте SQL, будет вызываться конструктор без параметров со следующими данными, назначенными общедоступным свойствам; и контейнер зависимостей недоступен на стороне SQL). Есть ли обходной путь для этого?

Технические детали моей реализации:

Пример конструктора базового класса Manager:

public abstract class ManagerBase : IManager
{
    protected ManagerBase(IUnityContainer container)
    {
        DependancyContainer = container;
    }

    protected readonly IUnityContainer DependancyContainer;

    ...
}

Пример базового класса репозитория:

public abstract class RepositoryBase<T, TDb> : IRepository<T>
    where T : IEntity
    where TDb : class, IDbEntity, new()
{
    protected abstract ITable<TDb> GetTable();

    public IQueryable<T> GetAll()
    {
        return GetTable().Select(GetConverter());
    }

Пример извлечения данных из БД: репозиторий создает экземпляры объектов и инициализирует их соответствующими данными БД, среди прочего инициализируется поле "Контейнер":

public class CountryRepository
    : RepositoryBase<ICountry, DbData.Country>, ICountryRepository
{
    protected override Expression<Func<DbData.Country, ICountry>> GetConverter()
    {
        return dbEntity => new Country
        {
            DependancyContainer = DependancyContainer,

            Code = dbEntity.CountryCode,
            Name = dbEntity.CountryName,
        };
    }

Когда требуется получить данные из БД, вызывается следующее:

public class CountryManager : ManagerBase
{
    ICountry GetCountryById(int countryId)
    {
        ICountryRepository repository = DependancyContainer.Resolve<ICountryRepository>();
        return repository.GetAll()
            .Where(country=>country.Id==countryId)
            .SingleOrDefault()
            ;
    }
}
  • Я хочу ответить на ваш вопрос, но не совсем понятно, что вы имеете в виду под '* Есть много случаев, когда требуется только 1-2 интерфейса (поэтому я не хочу создавать много других); ... create много другого, что в частности? James Webster 18.03.2011
  • Также я думаю, что ваш пример CountryManager должен расширять ManagerBase? James Webster 18.03.2011
  • CountryManager унаследован от managerBase. Это правильно. Budda 19.03.2011
  • 'Во многих случаях требуется только 1-2 интерфейса (поэтому я не хочу создавать много других); - это означает, что некоторым объектам необходимо знать 5 интерфейсов ( например): допустим, что объекту "Клиент" требуется доступ к "CustomerContacts" и "CustomerOrders". В одних случаях объект клиента создается для работы с заказом (потребуется интерфейс ICustomerOrderManager), в других - для работы с контактами (требуется интерфейс ICustomerContactManager). если эти интерфейсы необходимо будет передать в конструктор, мне всегда нужно будет инициализировать их оба ... Budda 19.03.2011

dependency-injectiondesign-patternsunity-container

Ответы

Ответ 1 · рейтинг 2 · James Webster

Q1: Другие могут не согласиться, но, как правило, вам не следует предоставлять контейнер DI своим моделям / классам объектов предметной области. Я вижу для этого две основные причины:

  1. Передача контейнера классу сущности скрывает зависимости класса сущности. Если вы полагаетесь на внедрение конструктора, вы можете увидеть, каковы его зависимости, посмотрев на конструктор. Если вы полагаетесь на внедрение свойств, вы можете увидеть, каковы его зависимости, посмотрев на свойства, хотя это менее ясно, чем внедрение конструктора (т.е. некоторые свойства являются данными класса сущности, некоторые свойства являются интерфейсами для других сервисы, относящиеся к классу сущности; разделение этих двух категорий несет некоторую когнитивную нагрузку). Если вы предоставите контейнер сущности, то зависимости будут разбросаны по всему классу.
  2. Проще проверить. Если вы передадите свой контейнер классу сущности, то для его тестирования вам придется имитировать / заглушить контейнер, а также интерфейсы, которые он извлекает из контейнера. Вы всегда можете настроить легкий контейнер, но это все еще больше работы, чем просто предоставление имитированных / заглушенных интерфейсов напрямую.

Q2: некоторые скажут, что хорошая практика (в DDD, откуда исходит шаблон репозитория / шаблон сильной модели предметной области) в первую очередь не подвергает ваш класс сущности интерфейсам. Скорее они должны содержать любую бизнес-логику, которую можно реализовать, не полагаясь на другие интерфейсы, а более сложная логика должна быть в классе обслуживания (на языке DDD).

Q3: вы не говорите, какой инструмент объектно-реляционного сопоставления вы используете, но можно получить некоторый контроль над процессом создания экземпляра объекта сущности с помощью какого-то типа перехватчика / шаблона аспектно-ориентированного программирования. . Это, тем не менее, привязывает вашу кодовую базу к тому, чтобы полагаться на этот конкретный преобразователь O / R и его возможности (то есть сложнее отказаться от него в будущем, хотя я обычно стараюсь не беспокоиться об этом при выборе архитектуры).

Еще несколько наблюдений о вашем примере кода, которые могут или не могут быть показательными для остальной части вашей кодовой базы:

  • Вы смотрели AutoMapper? Вы можете найти эту лучшую альтернативу ручному отображению, которое вы делаете в классе CountryRepository.GetConverter().
  • Похоже, вы создаете параллельные классы сущностей для каждого класса в пространстве имен DbData, предположительно потому, что вы не можете вставить нужные службы в классы пространства имен DbData. Возможно, мой ответ на Q2 направит вас в лучшую сторону (полагайтесь на службы и расширяйте DbData классы, если это возможно).
  • Я не совсем уверен, какую роль играет Manager ... в идеале все ваши методы запросов должны быть в репозитории, чтобы они действительно могли выполнять SELECT ... FROM Country where Id = ? запрос к базе данных, а не возвращать всю таблицу и выполнять запрос в памяти через LINQ, в конце концов, это то, в чем базы данных хороши! (несмотря на преднамеренную оптимизацию кэширования для характеристик производительности вашего приложения). Я считаю, что GetCountryById метод должен быть на самом CountryRepository.

Надеюсь, это поможет!

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы