Контейнер зависимостей: как создавать экземпляры объектов - PullRequest
3 голосов
/ 17 марта 2011

Я использую 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 голосов
/ 18 марта 2011

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

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

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

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

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

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

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

...