DI/IoC,NHibernate并帮助他们一起工作

5 c# nhibernate dependency-injection inversion-of-control

我正试图了解DI/IoC,NHibernate并让它们在我正在开发的应用程序中很好地协同工作.我对NHibernate和DI/IoC都很陌生,所以不太确定我所做的是否是合理的方式.这是场景:

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

用户从下拉列表中选择特定客户端和产品,并将相应的clientId和productId传递给存储库,然后使用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);
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

B. 是否可以使用IoC容器来获取具有注入的NHibernate填充/生成的产品和客户端依赖关系的Transaction对象?即,给定由用户提供的productId和clientId,是否可能具有如下内容:

// pseudocode
Transaction t = IoC.Resolve<Transaction>(productId, clientId);
Run Code Online (Sandbox Code Playgroud)

如果容器解析了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这实际上是如何显示DI的所有示例,假设IInterfaceB的实现者(在这种情况下为B类)在设计时是已知的.有没有办法以这样的方式使用DI,以便在运行时确定实现者?

非常感谢

马修

pla*_*ano 1

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

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

// 无需更改Transaction类
公开课交易
{
    私人双_margin;
    私人产品_产品;
    私人客户端_client;

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

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

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

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

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

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

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

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

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

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

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

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

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

// 使这个实现成为单例
[实现(typeof(MarginCalculator),LifecycleType.Singleton)]
公共类 ConcreteMarginCalculatorA :MarginCalculator
{
    公共重写 doubleCalculateMargin()
    {
        // 进行实际计算
    }
}

将所有代码编译到某个程序集中后,将其加载到容器中所需执行的操作如下:

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

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

int 产品ID = 12345;
int clientId = 54321;
字符串服务名称 = null;

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

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

您实际上可以在程序运行时交换实现,甚至可以替换实现而无需重新编译应用程序。您可以在这里找到更多信息:

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

哈:)