基类中的构造函数依赖注入

11
我正在构建一个基于 Entity Framework 的存储库基类,所有实体存储库都将继承该基类。我希望使用 Ninject 通过依赖注入将 DatabaseContext 注入到基类中。我认为构造函数注入是正确的方式,但是在派生类中使用构造函数注入时,我必须传递参数到基类的构造函数中,而我不想这样做。因此,Setter Injection 更合适吗?
这是我的代码:
public abstract class BaseRepository<TEntity> : IDisposable 
    where TEntity : class
{
    private readonly DatabaseContext _databaseContext;

    protected BaseRepository(DatabaseContext databaseContext)
    {
        this._databaseContext = databaseContext;
    }
}

在上面的示例中使用构造函数注入,在我的派生类中,我必须向基类传递databaseContext对象,但我不想对所有派生类都这样做:

在上述示例中使用构造函数注入,在我的派生类中,我必须将databaseContext对象传递给基类,但我不想在所有派生类中都这样做。

public class UsuarioRepository : BaseRepository<IUsuario>,
    IUsuarioRepository
{
    public UsuarioRepository(DatabaseContext databaseContext)
        : base(databaseContext)
    {
    }
}

使用Setter注入而不是构造函数注入是解决这个问题的好方法吗?哪种方法最好?

更新:

使用Setter注入后,我的派生类将没有构造函数:

public class UsuarioRepository : BaseRepository<IUsuario>, IUsuarioRepository
{
}

我的应用程序中只有一个上下文. 我不需要派生类来传递上下文对象,但我想将其注入基类以便将来使用模拟测试。

我解决了这个问题:

抱歉,我对问题感到困惑,但是我通过构建一个工厂来解决了我的问题:

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();
    }
}

我不太明白你的问题。为什么你不喜欢通过构造函数将DatabaseContext传递给基类?你是想避免将上下文注入到派生类中吗?我认为上面的代码没有问题,所以我不确定你具体要解决什么“问题”。 - fearofawhackplanet
我的派生类有一个基类参数。使用Setter注入,这种情况将不会发生,我想知道在这种情况下使用Setter注入是否是一个好的方法。 - Acaz Souza
将参数传递给基类有什么问题吗? - Phil
4个回答

18

属性注入意味着依赖关系是可选的,而构造函数注入意味着依赖关系是必需的。根据需要选择一种模式。

在95%以上的情况下,构造函数注入是正确的模式。您不喜欢它的哪一点呢?


4
我不喜欢在派生类中向基类传递参数,使用setter注入可以避免这种情况发生。 - Acaz Souza
2
@Acaz 个人偏好除外,构造器注入才是您情况下唯一适合的解决方案。 - Morten Mertner
4
这个问题在于,如果“BaseRepository<TEntity>”需要一个“DatabaseContext”的实例才能运作,那么这就是一个依赖项,而且不应该允许以无效状态实例化基类。构造函数用于创建可用的类型实例。使用设置注入只会引入需要开发人员明确了解他们需要在类型初始化中设置“DatabaseContext”的额外步骤。这对开发者来说并不是最理想的情况。因此,如果它是一个依赖项,请使用构造函数注入方法。 - Matthew Abbott
但问题是使用构造函数注入,所有派生类都将具有:base(object)代码。我想使用Setter Injection来实现所需的依赖关系。这篇文章就是我所谈论的:http://davybrion.com/blog/2009/11/constructor-injection-vs-sette-injection/ - Acaz Souza
6
是的,他们需要这样做,因为该依赖关系是“必需的”。由于该依赖关系是必需的,让编译器强制执行此不变量是一件好事。 - Mark Seemann

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;
    }
}

现在,您可以进行构造函数注入而不需要向基本构造函数传递参数。但实际上,这是相同的事情。
我真的不喜欢setter注入的想法,因为它看起来像DatabaseContext是一个必需的依赖项。对于必需的依赖项,请使用构造函数注入。如果它是可选的,那么请继续使用setter注入。
编辑:
由于我们在评论中进行了长时间的交谈,我更好地理解了您想要实现的目标。
如果您想将派生类与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的基类,而您的派生类不再依赖它。

@Acaz 我也不明白一件事:为什么DatabaseContext在应用程序中是唯一的很重要?这与将引用传递给构造函数无关。 - Phil
@Phil,没有从基类继承的方法来实现这个吗?你的例子不是很有用。 - Acaz Souza
+1 这是我会采取的方式,尽管我认为 OP 提出了一个不可能的问题。当然,使用属性注入将是一个非常糟糕的想法。 - fearofawhackplanet
这是一个不错的解决方案,如果它能够工作的话。但是它似乎非常依赖于你使用的DI框架,它是否允许你在容器中注入基础抽象类而不绑定任何实现,并且是否能够调用抽象类的构造函数(如果它将从注入类的上下文或一些无法调用此方法的静态解析器中进行调用?)这个问题测试过吗?在哪些框架中它能够工作/不能工作? - Robert Noack
@RobertNoack 我相信你可能在我的回答中发现了一个错别字。基类本不应该是抽象的。这只有在一个人放弃抽象类的想法并以不同的方式设计它时才能真正起作用。 - Phil
显示剩余12条评论

3

我认为没有“最好的方法”。

构造函数注入和setter注入并不完全等价,因为setter注入提供了更多的灵活性。以下是我在两者之间做出选择的方法:

假设您创建了一个对象A,A需要一个B对象才能工作。要问的问题是:A是否可以存在/实例化而不与B相关联?对于A对象没有B可能是一个有效状态吗?有时候A永远不会有一个空的B,那么构造函数注入是更好的解决方案。如果B可以在以后被分配给A而不一定是在构建时,那么就使用setter注入。所有这些都取决于您尝试建模的领域,答案将随着情况而变化。


问题是,我不喜欢在派生类中传递参数给基类构造函数。 - Acaz Souza

0
所有三种方式:1. 构造函数,2. Setter 和 3. 接口注入都有各自的优缺点,取决于情况来选择使用哪一种。如果我在你的位置上,我会选择 Setter,尽管接口也是一个不错的选择。
请您阅读这篇文章: http://www.devx.com/dotnet/Article/34066

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接