#c# #.net #dependency-injection #inversion-of-control
#c# #.net #внедрение зависимости #инверсия управления
Вопрос:
я создаю базовый класс репозитория с Entity Framework, где все репозитории объектов будут наследоваться. Я хочу внедрить DatabaseContext
в базовый класс с помощью внедрения зависимостей с использованием Ninject. Я думаю, что внедрение конструктора — это правильный путь, но, выполняя это с помощью внедрения конструктора в производный класс, я должен буду передать параметр конструктору в базовом классе, а я этого не хочу. Следовательно, внедрение установщика является более подходящим?
Вот мой код:
public abstract class BaseRepository<TEntity> : IDisposable
where TEntity : class
{
private readonly DatabaseContext _databaseContext;
protected BaseRepository(DatabaseContext databaseContext)
{
this._databaseContext = databaseContext;
}
}
Используя внедрение конструктора в приведенном выше примере, в моем производном классе я должен буду передать объект DatabaseContext в базовый класс, мне не нравится делать это со всеми моими производными классами:
public class UsuarioRepository : BaseRepository<IUsuario>,
IUsuarioRepository
{
public UsuarioRepository(DatabaseContext databaseContext)
: base(databaseContext)
{
}
}
Внедрение установщика вместо внедрения конструктора — хороший способ решить это? Каков наилучший способ?
Обновить:
Используя внедрение установщика, мой производный класс не будет иметь конструкторов:
public class UsuarioRepository : BaseRepository<IUsuario>, IUsuarioRepository
{
}
Мой контекст только один во всем приложении. Мне не нужен производный класс для передачи объекта контекста, но я хотел бы внедрить его в базовый класс для использования mocks для тестов в будущем.
Я решаю проблему:
Извините, я запутался в вопросе, но я решаю свою проблему, создавая фабрику:
public abstract class BaseRepository<TEntity> : IBaseRepository<TEntity>
where TEntity : class
{
private readonly ContextFactory _contextFactory = new ContextFactory();
protected DatabaseContext CurrentContext
{
get
{
return this._contextFactory.GetCurrentContext();
}
}
}
И мой производный класс будет выглядеть примерно так:
public class UsuarioRepository : BaseRepository<IUsuario>, IUsuarioRepository
{
}
И моей фабрике нравится это:
public class ContextFactory
{
public DatabaseContext GetCurrentContext()
{
return new DatabaseContext();
}
}
Комментарии:
1. Я не совсем понимаю ваш вопрос. Почему вам не нравится передавать DatabaseContext в базовый класс через конструктор? Вы пытаетесь избежать необходимости вводить контекст в производный класс? Я не вижу ничего плохого в приведенном выше коде, поэтому я не уверен, какую «проблему» вы конкретно пытаетесь решить.
2. Мой производный класс имеет параметр для базового класса. При использовании внедрения установщика этого не произойдет, я хочу знать, является ли использование внедрения установщика в этой ситуации хорошим способом.
3. Что плохого в передаче параметра базовому классу?
Ответ №1:
Внедрение свойства подразумевает, что зависимость необязательна, в то время как внедрение конструктора подразумевает, что зависимость обязательна. Выберите шаблон соответствующим образом.
Более чем в 95% случаев внедрение конструктора является правильным шаблоном. Что в этом вам не нравится?
Комментарии:
1. Мне не нравится передавать параметр базовому классу в моем производном классе, используя внедрение установщика, этого не произойдет.
2. @Acaz если отбросить личные предпочтения, внедрение конструктора действительно является единственным подходящим решением для вашего сценария.
3. @Acaz Проблема заключается в том, что если для работы
BaseRepository<TEntity>
требуется экземплярDatabaseContext
, то это зависимость, и вы не должны допускать создания экземпляра базового класса с недопустимым состоянием. Конструкторы используются для создания экземпляра типа, который должен использоваться. Использование установки инъекции просто вводит необходимость для разработчика точно знать, что им нужно установитьDatabaseContext
в качестве дополнительного шага при инициализации типа. В идеале это не самое лучшее для разработчика. Просто, если это зависимость, используйте внедрение конструктора.4. Но проблема в том, что при использовании внедрения конструктора весь мой производный класс будет иметь: базовый (объектный) код. Я хочу использовать внедрение установщика для требуемой зависимости. Этот пост — это то, о чем я говорю: davybrion.com/blog/2009/11 /…
5. @Acaz Souza: Да, им это понадобится, потому что зависимость обязательна . Поскольку зависимость обязательна, хорошо , чтобы компилятор применял этот инвариант.
Ответ №2:
Я не думаю, что есть «лучший способ».
Внедрение конструктора и установщика не совсем эквивалентны, поскольку внедрение установщика обеспечивает вам немного большую гибкость. Вот как я выбираю между двумя:
Допустим, вы создаете объект A, и A нуждается в объекте B для работы. Вопрос, который следует задать: возможно ли существование / создание экземпляра A без связанного с ним B? Допустимо ли, чтобы объект A не имел B? Иногда для A не имеет смысла когда-либо иметь null B, тогда лучшим решением является внедрение конструктора. Если можно, чтобы B было назначено A позже и не обязательно во время построения, тогда используйте внедрение установщика. Все это зависит от домена, который вы пытаетесь смоделировать, и ответы будут меняться от ситуации к ситуации.
Комментарии:
1. Вопрос в том, что мне не нравится шум моего производного класса, передающего параметр конструкторам базового класса.
Ответ №3:
Действительно, я не вижу никакой проблемы с передачей параметра базовому классу. Но если это абсолютное требование, вы всегда можете сделать это:
public abstract class BaseRepository<TEntity> : IDisposable
where TEntity : class
{
protected BaseRepository()
{
}
protected abstract DatabaseContext GetContext();
}
Теперь ваши производные классы могут выглядеть следующим образом:
public class UsuarioRepository : BaseRepository<IUsuario>,
IUsuarioRepository
{
private DatabaseContext _databaseContext;
public UsuarioRepository(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
protected override DatabaseContext GetContext()
{
return _databaseContext;
}
}
Теперь вы можете выполнить внедрение конструктора без передачи параметра базовому конструктору. Но на самом деле это одно и то же.
Мне, честно говоря, не нравится идея внедрения установщика, потому что похоже, что DatabaseContext является обязательной зависимостью. Для получения требуемых зависимостей выполните внедрение конструктора. Если бы это было необязательно, то, во что бы то ни стало, продолжайте и выполните внедрение установщика.
Редактировать:
Благодаря нашему долгому разговору в комментариях я намного лучше понимаю, чего вы пытаетесь достичь.
Если вы хотите отделить свои производные классы от DatabaseContext, возможно, вам было бы лучше спроектировать его по-другому. Сложно отделить производный класс от зависимостей его базового класса, и если вы считаете, что они должны быть отделены, тогда у вас будет лучший дизайн, не использующий наследование вообще.
public class BaseRepository<TEntity> : IDisposable
where TEntity : class
{
public BaseRepository(DatabaseContext context)
{
}
}
Теперь ваши производные классы (которые больше не будут производными) будут выглядеть следующим образом:
public class UsuarioRepository : IUsuarioRepository
{
public UsuarioRepository(BaseRepository<IUsario> repository)
{
}
// Implement other methods here
}
Теперь у вас может быть свой единственный базовый класс, который использует DatabaseContext, и ваши производные классы больше не зависят от него.
Комментарии:
1. Мой контекст — ЭТО ТОЛЬКО ОДИН из всех приложений! Мне не нужен производный класс для передачи контекста.
2. @Phil Почему я буду передавать параметр в конструктор моего базового класса, если этот параметр ТОЛЬКО ОДИН во всем приложении? Почему бы не перейти непосредственно в базовый класс?
3. @Acaz Потому что это обязательная зависимость. Цель конструктора — гарантировать, что все требуемые зависимости предоставлены классу во время построения — другими словами, конструкторы предотвращают возможность существования объекта без правильных требуемых зависимостей. Мой вопрос таков… почему бы НЕ передать параметр базовому конструктору? Что в этом плохого?
4. @Acaz Я тоже кое-чего не понимаю: почему важно, чтобы DatabaseContext был ЕДИНСТВЕННЫМ в приложении? Это не имеет ничего общего с передачей ссылок на конструкторы.
5. @Phil Почему БЫ И НЕТ? Потому что объект DatabaseContext ЯВЛЯЕТСЯ ТОЛЬКО ОДНИМ во всем приложении. Весь производный класс должен использовать единственный существующий объект DatabaseContext, другого нет. Поскольку прямая передача объекта DatabaseContext в базовый класс намного лучше, мне больше не нужно беспокоиться об объекте DatabaseContext. Если я захочу заменить объект DatabaseContext завтра, я сделаю это только в одном месте, избегая изменений во всех производных классах. Вы понимаете?
Ответ №4:
Все три способа: 1. Конструктор, 2. Установщик и 3. Внедрение интерфейса имеет свои преимущества и недостатки, это зависит от ситуации, какой из них использовать. Если бы я был на вашем месте, я бы выбрал setter, хотя interface также может быть хорошим выбором.
Прошу вас ознакомиться с этой статьей http://www.devx.com/dotnet/Article/34066