依赖注入和项目引用

7

我正在学习依赖注入(DI),以更好地理解控制反转(IoC)和其他优势。

在使用 DI 之前,我的项目包括一个 UI 项目(MVC),一个业务逻辑项目和一个数据访问项目。还有一个共享库项目。所有项目都引用了共享库。UI 引用了 BusinessLogic,而 BusinessLogic 引用了 DataAccess。

现在,我想添加接口。因此,我去我的 DataAccess 中,为每个类添加了一个接口,并填充了它们的方法。我对业务逻辑层也做了同样的操作。

但是为了注入 DataAccess 类,在UI 项目中的 BusinessLogic 类中实例化它,我需要我的 Data 项目的引用,因为 UI 项目(我认为是正确的)不知道什么是 'IDataAccess' 接口。我能想到的唯一解决方法是在我的 UI 项目中添加一个对 DA 项目的项目引用 - 这似乎是错误的。

如果我尝试将 Unity 作为容器 (未来的某一天,一旦我弄清楚了所有这些), 并想要在 UI 项目中初始化我的接口/类关系 - 同样的问题。

也许接口必须放在某个共享项目中?或者上面的一个项目?应该如何处理?


项目引用(例如循环引用)和DI是两个独立的问题。添加引用并不一定是件坏事。正确的答案取决于DI使用的工具。我建议您调查您熟悉的任何DI工具,并根据您的需求评估其功能集。 - Trevor Ash
稍微解释一下我上面所说的;使用许多 DI 工具,你必须在某个地方将服务注册到接口。注册后,你不需要手动创建实例。当你正确使用 DI 时,你的 UI 将不会创建业务对象的实例,而是将其注入到 UI 控制器的构造函数中,就像 DataAccess 将被注入到业务逻辑类中一样。例如,花一点时间了解 DI 工具如何运作,特别是对于 MVC 应用程序,因为它们是一种特殊的存在。 - Trevor Ash
你也可以将你的类设为所属程序集的内部类,只将接口公开为public。像Autofac这样的DI容器可以连接程序集中的所有类,并仅通过它们的接口提供访问。 - Ian Mercer
1个回答

9
如果您不想在项目之间使用引用,可以考虑使用工厂/抽象工厂。
您的UI知道您的业务层,因此您希望在业务层中定义一个工厂,该工厂知道如何使用数据层。然后,在组合根(此示例中的UI项目)中处理所有DI。
以下是一个简单的示例,将控制台应用程序用作UI,坚持您在问题中提到的引用。
数据层:
public interface IDataAccess
{
    string GetData();
}

public class XmlDataAccess : IDataAccess
{
    public string GetData()
    {
        return "some data";
    }
}

业务层

public interface IDataAccessFactory
{
    IDataAccess GetDataAccess();
}

public class XmlDataAccessFactory : IDataAccessFactory
{
    public IDataAccess GetDataAccess()
    {
        return new XmlDataAccess();
    }
}

public class BusinessLogic
{
    IDataAccessFactory dataAccessFactory;

    public BusinessLogic(IDataAccessFactory dataAccessFactory)
    {
        this.dataAccessFactory = dataAccessFactory;
    }

    public void DoSomethingWithData()
    {
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        Console.WriteLine(dataAccess.GetData());
    }

    public string GetSomeData()
    {
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        return dataAccess.GetData();
    }
}

UI

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    container.RegisterType<IDataAccessFactory, XmlDataAccessFactory>();

    var logic = container.Resolve<BusinessLogic>();
    logic.DoSomethingWithData();

    string useDataInUI = logic.GetSomeData();
    Console.WriteLine("UI " + useDataInUI);

    Console.ReadKey();
}

这只是一个人为制造的例子,看起来像是无用的抽象,但在实际应用中会更有意义。
例如,你可能在数据层数据库、XML文件等中有许多不同的数据访问类,因此你可以在业务层为每个定义一个工厂。请注意,保留HTML标签。

使用抽象工厂

工厂可以包含更多关于数据层细节的逻辑,或者作为抽象工厂向业务逻辑层提供一组单独的工厂。

业务逻辑层

您也可以在业务逻辑层中拥有一个抽象工厂,例如

public interface IPlatformFactory
{
    IDataAccessFactory GetDataAccessFactory();
    IPricingFactory GetPricingFactory(); // might be in the business project, or another project referenced by it
}

使用具体工厂。
public class WebPlatformFactory : IPlatformFactory
{
    IDataAccessFactory GetDataAccessFactory()
    {
        return new XmlDataAccessFactory();
    }

    IPricingFactory GetPricingFactory()
    {
        return new WebPricingFactory(); // not shown in the example
    }
}

你可能还有其他具体的工厂,比如 RetailPlatformFactory 等。

你的 BusinessLogic 类现在会看起来像这样:

public class BusinessLogic
{
    IPlatformFactory platformFactory;

    public BusinessLogic(IPlatformFactory platformFactory)
    {
        this.platformFactory = platformFactory;
    }

    public void DoSomethingWithData()
    {
        IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        Console.WriteLine(dataAccess.GetData());
    }

    public string GetSomeData()
    {
        IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        return dataAccess.GetData();
    }
}

数据层

在这个例子中,您的业务层不再需要向用户界面提供一个 IDataAccessFactory,因此可以将其移动到数据层。因此,数据层类将会是:

public interface IDataAccess
{
    string GetData();
}

public class XmlDataAccess : IDataAccess
{
    public string GetData()
    {
        return "some data";
    }
}

public interface IDataAccessFactory
{
    IDataAccess GetDataAccess();
}

public class XmlDataAccessFactory : IDataAccessFactory
{
    public IDataAccess GetDataAccess()
    {
        return new XmlDataAccess();
    }
}

用户界面(UI)

现在,您将在用户界面(UI)中配置容器并执行类似的操作。

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    container.RegisterType<IPlatformFactory, WebPlatformFactory>();

    var logic = container.Resolve<BusinessLogic>();
    logic.DoSomethingWithData();

    string useDataInUI = logic.GetSomeData();
    Console.WriteLine("UI " + useDataInUI);

    Console.ReadKey();
}

UI并不了解数据层/访问,它只是将工厂创建交给业务层,后者持有数据(和定价)的引用。
一些推荐阅读: 组合根 实现抽象工厂 自信地组成对象图

很棒的帖子!我正在仔细阅读。谢谢。 - Craig

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