Инъекция зависимости с несколькими репозиториями - PullRequest
6 голосов
/ 27 февраля 2010

У меня есть служба wcf, и на клиенте у меня есть:

var service = new ServiceReference1.CACSServiceClient()

Фактический код услуги:

public CACSService() : this(new UserRepository(), new BusinessRepository()) { }

public CACSService(IUserRepository Repository, IBusinessRepository businessRepository)
{
     _IRepository = Repository;
     _IBusinessRepository = businessRepository;
}

Итак, все это прекрасно работает, но мне не нравится, как я обновляю все репозитории одновременно, потому что клиентскому коду может не понадобиться обновлять UserRepository и он заинтересован только в обновлении BusinessRepository. Итак, есть ли способ передать что-то этому коду:
var service = new ServiceReference1.CACSServiceClient()
чтобы сказать ему, какой репозиторий для новых, основан на коде, который вызывает сервис, или любой другой совет, который мне нужен при проектировании репозиториев для моей структуры сущностей. Thankx

Ответы [ 4 ]

16 голосов
/ 28 февраля 2010

Прелесть чистого DI в том, что вам не нужно беспокоиться о времени жизни ваших зависимостей , потому что ими управляет тот, кто их поставляет (Контейнер DI или какой-то другой код, который вы написали сами) ).

(Кроме того, вы должны избавиться от своих текущих Bastard Injection . Выбросьте конструктор без параметров и оставьте тот, который явно объявляет его зависимости.)

Держите ваш конструктор таким, как он, и используйте _IRepository и _IBusinessRepository по мере необходимости:

public CACSService(IUserRepository Repository, IBusinessRepository businessRepository) 
{ 
    _IRepository = Repository; 
    _IBusinessRepository = businessRepository; 
} 

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

Предположим, что IUserRepository выглядит следующим образом:

public interface IUserRepository
{
    IUser SelectUser(int userId);
}

Теперь вы можете реализовать реализацию с отложенной загрузкой, например:

public class LazyUserRepository : IUserRepository
{
    private IUserRepository uRep;

    public IUser SelectUser(int userId)
    {
        if (this.uRep == null)
        {
            this.uRep = new UserRepository();
        }
        return this.uRep.SelectUser(userId);
    }
}

Когда вы создаете CACService, вы можете сделать это, вставив в него LazyUserRepository, который гарантирует, что реальный UserRepository будет инициализирован только в случае необходимости.

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

Я впервые описал технику Ленивые зависимости здесь и здесь .

0 голосов
/ 27 февраля 2010

Есть ли в ваших репозиториях состояние уровня объекта? Вероятно, нет, поэтому создайте их как синглтоны и сделайте так, чтобы контейнер DI предоставил их CACService.

Иначе, действительно ли они дороги в создании? В противном случае создание нового для каждого запроса обходится незначительно по сравнению с операциями RPC и базы данных.

При использовании контейнера внедрения зависимостей Ninject ваш CACService может выглядеть следующим образом. Другие DI-контейнеры имеют одинаково лаконичные механизмы для этого.

public class CACSService
{
    public CACService
    {
        // need to do this since WCF creates us
        KernelContainer.Inject( this );
    }

    [Inject]
    public IUserRepository Repository
    { set; get; }

    [Inject]
    public IBusinessRepository BusinessRepository
    { set; get; }
}

И во время запуска приложения вы сообщаете Ninject об этих типах.

Bind<IUserRepository>().To<UserRepository>().InSingletonScope();
Bind<IBusinessRepository>().To<BusinessRepository>().InSingletonScope();
0 голосов
/ 27 февраля 2010

Предисловие : это общее руководство по инверсии зависимостей. Если вам нужен конструктор по умолчанию для выполнения работы (например, если он был создан с помощью отражения или чего-то еще), тогда будет сложнее сделать это чисто.

Если вы хотите сделать ваше приложение настраиваемым, это означает, что вы можете изменять способ построения графа вашего объекта. Проще говоря, если вы хотите варьировать реализацию чего-либо (например, иногда вам нужен экземпляр UserRepository, в других случаях вам нужен экземпляр MemoryUserRepository), то тип, который использует реализация (CACService в данном случае) не должна быть обвинена в ее обновлении. Каждое использование new привязывает вас к конкретной реализации. Миско написал несколько хороших статей по этому вопросу .

Принцип инверсии зависимостей часто называют «параметризацией сверху», так как каждый конкретный тип получает свои (уже созданные) зависимости от вызывающей стороны.

Чтобы применить это на практике, переместите код создания объекта из конструктора CACService без параметров и поместите его на фабрику.

Затем вы можете по-разному подключать вещи, основываясь на таких вещах, как:

  • чтение в файле конфигурации
  • передача аргументов фабрике
  • создание фабрики другого типа

Разделение типов на две категории (типы, которые создают вещи и типы, которые делают вещи) - мощная техника.

например. Вот один относительно простой способ сделать это с помощью фабричного интерфейса - мы просто создаем новый, какой бы завод ни подходил для наших нужд, и вызываем его метод Create. Мы используем контейнер Dependency Injection ( Autofac ), чтобы делать это на работе, но он может быть излишним для ваших нужд.

public interface ICACServiceFactory
{
    CACService Create();
}

// A factory responsible for creating a 'real' version
public class RemoteCACServiceFactory : ICACServiceFactory
{
    public CACService Create()
    {
         return new CACService(new UserRepository(), new BusinessRepository());
    }        
}

// Returns a service configuration for local runs & unit testing
public class LocalCACServiceFactory : ICACServiceFactory
{
    public CACService Create()
    {
         return new CACService(
               new MemoryUserRepository(), 
               new MemoryBusinessRepository());
    }     
}
0 голосов
/ 27 февраля 2010

Вместо того, чтобы создавать («обновлять») репозитории при создании, вы можете лениво загружать их в их свойствах. Это позволит вам сохранить ваш второй конструктор, но ваш первый конструктор ничего не сделает.

Пользователь может затем назначить их по мере необходимости, в противном случае.

Например:

public class CACSService
{
    public CACSService() {}

    public CACSService(IUserRepository Repository, IBusinessRepository businessRepository)
    {
        _IRepository = Repository;
        _IBusinessRepository = businessRepository;
    }

    private IUserRepository _IRepository;
    public IUserRepository Repository
    {
        get {
             if (this._IRepository == null)
                  this._IRepository = new UserRepository();
             return this._IRepository;
        }
    }

   // Add same for IBusinessRepository
}
...