5

我试图让我的头脑围绕 DI/IoC、NHibernate 并让它们为我正在开发的应用程序很好地协同工作。我对 NHibernate 和 DI/IoC 都很陌生,所以不太确定我正在做的事情是否是明智的做法。这是场景:

该应用程序为用户提供了计算特定金融交易的特定价值(称为保证金)的能力。每个交易的保证金值的计算是通过抽象 MarginCalculator 类的具体实现来执行的,而要使用的具体实现取决于特定交易的产品类型(由产品对象的某个字段给出)。具体的计算器类是通过产品类的属性访问的。IE

public class Transaction
{
    private double _margin;
    private Product _product;
    private Client _client;

    public double Margin { get; }
    public Product Product { get; }
    public Client Client { get; }

    public Transaction(Product p, Client c)
    {
        _product = p;
        _client = c;
    }

    public void CalculateMargin()
    {
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

public class Product
{
    private string _id;
    private string _productType;
    ... Other fields

    public string Id { get; }
    public string ProductType { get; }
    public MarginCalculator MarginCalculator
    {
        get { return MarginCalculatorAssembler.Instance.CreateMarginCalculatorFor(this.ProductType); }
    }
}

public class MarginCalculatorAssembler
{
    public static readonly MarginCalculatorAssembler Instance = new MarginCalculatorAssembler();

    private MarginCalculatorAssembler ()
    {
    }

    public MarginCalculator CreateMarginCalculatorFor(string productType)
    {
        switch (productType)
        {
            case "A":
                return new ConcreteMarginCalculatorA();
            case "B":
                return new ConcreteMarginCalculatorB();
            default:
                throw new ArgumentException();
        }
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

public class ConcreteMarginCalculatorA : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

用户从下拉列表中选择特定的客户端和产品,并将相应的客户端 ID 和产品 ID 传递到存储库,然后使用 NHibernate 填充产品和客户端对象,然后再将它们注入事务对象。在我当前的设置中,事务通过构造函数依赖注入(尚未使用 IoC 容器)接收其产品和客户端依赖项,即

public class ProductRepository : IRepository<Product>
{
    public Product GetById(string id)
    {
        using (ISession session = NHibernateHelper.OpenSession())
            return session.Get<Product>(id);
    }
}

/* Similar repository for Clients */

IRepository<Client> clientRepository = new ClientRepository();
IRepository<Product> productRepository = new ProductRepository();
Client c = clientRepository.GetById(clientId);
Product p = productRepository.GetById(productId);

Transaction t = new Transaction(p, c);

以下是我希望得到的想法:

A. 是否可以通过 Product 域对象访问 MarginCalculator(本质上是一项服务),或者应该按照此处的建议(http://stackoverflow.com/questions/340461/dependency-injection-with-nhibernate -objects ) 重构代码以从域对象中删除服务依赖项,而是创建一个新的 TransactionProcessor 类,该类将抽象 MarginCalculator 作为依赖项(按照此处描述的内容 ( http://www.lostechies.com ) /blogs/jimmy_bogard/archive/2008/03/31/ptom-the-dependency-inversion-principle.aspx)即

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;

    public TransactionProcessor(MarginCalculator marginCalculator)
    {
        _marginCalculator = marginCalculator;
    }

    public double CalculateMargin(Transaction t)
    {
        return _marginCalculator.CalculateMargin(Transaction t);
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin(Transaction t);
}

B. 是否可以使用 IoC 容器来获取注入 NHibernate 填充/生成的产品和客户端依赖项的事务对象?即给定一个由用户提供的productId和clientId,是否有可能有类似的东西:

// pseudocode
Transaction t = IoC.Resolve<Transaction>(productId, clientId);

这样容器就解决了 Transaction 对象的 Product 和 Client 依赖关系,NHibernate 用于根据 productId 和 clientId 填充 Product 和 Client,然后将填充的 Product 和 Client 注入到 Transaction?

C.在典型的 DI 场景中,如果类 A 依赖于接口 B,则可能会执行以下操作:

IInterfaceB b = new ClassB();
A a = new A(b);

interface IInterfaceB
{
}

class B : IInterfaceB
{
}

public class A
{
    private IIntefaceB _b;

    public A(IInterfaceB b)
    {
        _b = b;
    }
}

但是,这实际上是所有 DI 示例的显示方式,它假定 IInterfaceB 的实现者(在本例中为 B 类)在设计时是已知的。有没有办法以在运行时确定实现者的方式使用 DI?

非常感谢

马修

4

6 回答 6

1

A) 如果你打算通过 Product 域对象访问 MarginCalculator,你不妨去掉中间人,让 DI/IOC 容器为你注入 MarginCalculator。您甚至可以摆脱 MarginCalculatorAssembler,因为大多数 DI/IOC 容器为您完成了对象构造的大部分样板代码。

B 和 C ) 很有可能。事实上,如果您使用LinFu ,您的代码如下所示:

// 无需更改 Transaction 类
公共类事务
{
    私人双倍保证金;
    私有产品_product;
    私人客户_client;

    公共双倍保证金 { 得到;}
    公共产品产品 { 获取;}
    公共客户客户{得到; }

    公共交易(产品 p,客户 c)
    {
        _产品 = p;
        _client = c;
    }

    公共无效计算保证金()
    {
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

如果您可以使用 DI/IOC 将产品和客户端实例注入到构造函数中,那就太好了——但在我们这样做之前,您需要向容器注册依赖项。以下是使用 LinFu.IOC 的方法:

// 接下来,您必须告诉 LinFu 自动注册您的产品类:
[工厂(类型(产品))]
公共类 ProductFactory : IFactory
{
     对象 CreateInstance(IServiceRequest 请求)
     {
          // 从容器中获取 IRepository 的副本
          var repository = container.GetService>();

          // 获取 id(假设你的 id 是 Int32)
          var id = (int)request.Arguments[0];

          // 返回产品本身
          返回存储库.GetById(id);
     }
}

// 对 Client 类做同样的事情
//(注意:为了简单起见,我做了一个简单的剪切和粘贴——请原谅重复)
[工厂(类型(客户端))]
公共类 ClientFactory : IFactory
{
     对象 CreateInstance(IServiceRequest 请求)
     {
          // 从容器中获取 IRepository 的副本
          var repository = container.GetService>();

          // 获取 id(假设你的 id 是 Int32)
          var id = (int)request.Arguments[0];

          // 返回客户端本身
          返回存储库.GetById(id);
     }
}

[工厂(类型(交易))]
公共类 TransactionFactory : IFactory
{
     对象 CreateInstance(IServiceRequest 请求)
     {
        // 注意:为简洁起见,已删除参数检查
        var 容器 = request.Container;
        var arguments = request.Arguments;
        var productId = (int)arguments[0];
        var clientId = (int)arguments[1];

        // 获取产品和客户端
        var product = container.GetService(productId);
        var client = container.GetService(clientId);

        // 创建交易本身
        返回新交易(产品,客户);
     }
}

// 使这个实现成为一个单例
[实现(typeof(MarginCalculator), LifecycleType.Singleton)]
公共类 ConcreteMarginCalculatorA : MarginCalculator
{
    公共覆盖双CalculateMargin()
    {
        // 执行实际计算
    }
}

在您的一个程序集中编译完所有代码后,您只需执行以下操作即可将其加载到容器中:

var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDIrectory, "YourAssembly.dll");

...现在是有趣的部分。为了使用给定的产品和客户 ID 创建您的交易对象,您需要对 LinFu.IOC 的容器进行以下调用:

整数产品 ID = 12345;
int 客户端 ID = 54321;
字符串服务名称=空;

// 不是伪代码 :)
var transaction = container.GetService(serviceName, productId, clientId);

有趣的是,尽管您可能拥有大量依赖项,但 LinFu 的 IOC 容器将为您处理 90% 的样板代码,因此您不必自己做所有这些事情。最好的部分是上面的所有实现都将在运行时确定/解决

您实际上可以在程序运行时交换实现,甚至可以在不重新编译应用程序的情况下替换实现。您可以在此处找到更多信息:

http://www.codeproject.com/KB/cs/LinFu_IOC.aspx

HTH :)

于 2008-12-23T01:15:26.843 回答
1

这是我对您的问题的第二次看法:

答:就最佳实践而言,只要确保依赖于接口类型,就可以将服务依赖项留在域对象中。大多数(如果不是全部)容器可以为您执行这种类型的注入,并且模拟每个服务依赖项非常简单,因此您可以测试具体类中的每个行为。如果您想重构特定接口实现的样板实现,例如使用基类来执行通用 CRUD 持久性工作,我只建议使用抽象类。

乙和丙:

很高兴知道这种功能是可用的。我想一个更重要的问题是我正在尝试做的实际上是否是常见的做法,以及它是否被认为是好的做法。IE

  1. 让容器解析并注入已预先填充的依赖项 > 使用持久性框架(例如 NHibernate)和
  2. 让容器注入抽象依赖项的具体实现,具体实现在运行时确定。

另外,在 IoC/DI/NHibernate 术语中,我在说什么,有一个特定的名称吗?例如,它是此比较或 .net IoC 框架比较中列出的功能之一吗?我想了解其他 IoC 框架(如 Castle Windsor)是否像 LinFu 那样包含这些功能,但我不知道我所描述的内容是否具有特定名称,所以我不知道要搜索什么:)

我相信您实际上指的是此链接上发布的比较。

1) AFAIK,进行服务注入是标准做法,但是您所指的注入类型对于其他一些框架来说很难做到,因为您必须在运行时使用域对象 ID 来解决这些依赖关系,并不是所有的容器都支持这种类型的动态解析(又名“上下文绑定”)。在所有条件相同的情况下(并假设这可以通过其他容器完成),似乎适用于 DI/IoC 的唯一“最佳实践”是您必须为服务依赖项使用接口。

这些依赖项最终应该如何构建和解决应该完全取决于您,并且在您的情况下,是否从持久性框架中填充这些依赖项并不重要,只要容器本身能够消除大部分为您提供样板解析代码。

2)具体的服务注入是DI/IOC框架中的标配,大部分可以在运行时解析依赖;但是,这些框架在注入的方式和位置上有所不同。

仅供参考,您应该注意的两个功能是构造函数注入属性注入。根据您的代码示例,我会说您更倾向于使用构造函数注入,因此您可能需要密切注意每个各自的框架如何为您执行这种类型的注入。HTH :)

于 2008-12-25T00:48:21.563 回答
0

巴勃罗,

谢谢你的评论。

也许如果我详细说明我打算在项目中使用 DI 的一个领域(正如你所说,不仅要了解 DI,而且因为我认为这是必要的),然后可以就它是否是使用 DI 的正确位置。

如原始帖子中所述,该应用程序将使用 MarginCalculator 服务:

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

注意:服务可能是抽象类或接口。

具体实现(DI 术语中的组件?)如下:

public class ConcreteMarginCalculatorA : MarginCalculator
{
    private IDependencyService1 _dependencyService1;
    private IDependencyService2 _dependencyService2;

    // Constructor dependency injection
    public ConcreteMarginCalculatorA(
        IDependencyService1 dependencyService1,
        IDependencyService2 dependencyService2)
    {
        this._dependencyService1 = dependencyService1;
        this._dependencyService2 = dependencyService2;
    }

    public override double CalculateMargin
    {
        // _dependencyService1 and _dependencyService2 
        // required here to perform calcuation.
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    private IDependencyService3 _dependencyService3;
    private IDependencyService4 _dependencyService4;

    // Constructor dependency injection
    public ConcreteMarginCalculatorB(
        IDependencyService3 dependencyService3,
        IDependencyService4 dependencyService4)
    {
        this._dependencyService3 = dependencyService3;
        this._dependencyService4 = dependencyService4;
    }

    public override double CalculateMargin
    {
        // _dependencyService3 and _dependencyService4 
        // required here to perform calcuation.
    }
}

具体的保证金计算器及其构造难道不是应该在何处使用依赖注入以及如何使用 IoC 容器来处理依赖注入的完美示例吗?

我认为我正在尝试做的与本文本文等文章中描述 DI/IoC 的方式非常相似

最后,我将使用一个工厂类,可能带有一个内部/子容器,以便根据参数值动态解析组件/实现器(ConcreteMarginCalculatorA、ConcreteMarginCalculatorB 等...)。为了实现这一点,我倾向于Autofachttp://code.google.com/p/autofac/),它允许根据参数值(http://code.google.com/p/autofac选择实现者/wiki/ComponentCreation - “基于参数值选择实施者”部分):

public class MarginCalculatorFactory
{
    private readonly IContainer _factoryLevelContainer;

    public MarginCalculatorFactory(IContainer mainContainer)
    {
        _factoryLevelContainer = mainContainer.CreateChildContainer()
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorA>("ConcMC1");
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorB>("ConcMC2");
}

public MarginCalculator CreateCalculator(string productType)
{
    return _factoryLevelContainer.Resolve<MarginCalculator>(productType);
}

}

所以最后我可以做到:

marginCalculatorFactory.CreateCalculator(productType);

在客户端代码中并获得一个完全解析的计算器。然后计算器可以反过来被依赖注入到 TransactionProcessor 服务中:

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;
    private readonly Transaction _transaction;

    public TransactionProcessor(MarginCalculator marginCalculator
        ,Transaction transaction)
    {
            _marginCalculator = marginCalculator;
            _transaction = transaction
    }

    public double CalculateMargin(Transaction t)
    {
            return _marginCalculator.CalculateMargin(transaction);
    }
}

我可能是错的,因为我是整个 IoC/DI 游戏的新手,但在我看来,这正是Di/IoC 用于的那种场景。别人怎么看?

谢谢

马修

于 2008-12-23T16:41:46.817 回答
0

看看这篇文章 http://fabiomaulo.blogspot.com/2008/11/entities-behavior-injection.html

于 2008-12-25T13:45:09.513 回答
0

菲利普,

感谢您的回答!

乙和丙

很高兴知道这种功能是可用的。我想一个更重要的问题是我正在尝试做的实际上是否是常见的做法,以及它是否被认为是好的做法。IE

  1. 让容器解析并注入使用持久性框架(例如 NHibernate)预先填充的依赖项,并且
  2. 让容器注入抽象依赖项的具体实现,具体实现在运行时确定。

另外,在 IoC/DI/NHibernate 术语中,我在说什么,有一个特定的名称吗?例如,它是此比较或.net IoC 框架比较中列出的功能之一吗?我想了解其他 IoC 框架(如 Castle Windsor)是否像 LinFu 那样包含这些功能,但我不知道我所描述的内容是否具有特定名称,所以我不知道要搜索什么:)

A:

就最佳实践(即松散耦合、测试等)而言,从域对象中删除服务依赖项或将其保留在那里会更好吗?

谢谢

马修

于 2008-12-23T07:51:33.660 回答
0

根据“域驱动设计”,您的服务将是“域服务”,您的域的其余部分可以直接调用它或依赖它。

如果您打算使用 Nhibernate,请查看 Spring.net,这是一个非常流行的 DI 框架,它为您提供 DAOS,它已经在它们上注入了一个会话。它还允许您使用声明性事务(使用属性标记方法)。该项目的文档非常好。

最后但并非最不重要的一点,不要误会我的意思,我认为您使用该技术只是因为(我不认为您有 DI 的需要),如果您这样做是为了学习东西,这很酷,但在其他所有情况下都是错误的。

问候

于 2008-12-23T13:18:21.143 回答