【问题标题】:DI/IoC, NHibernate and help in getting them to work togetherDI/IoC、NHibernate 并帮助他们一起工作
【发布时间】:2008-12-22 23:37:07
【问题描述】:

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

该应用程序为用户提供了计算特定金融交易的特定价值(称为保证金)的能力。每笔交易的保证金值的计算是通过抽象 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
    }
}

用户从下拉列表中选择特定的客户端和产品,并将相应的 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);

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

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?

非常感谢

马修

【问题讨论】:

  • 您可能希望首先使用“DI”或“IOC”的实际单词,然后使用它们的缩写。我知道 IoC 是什么,但 DI 是什么?
  • 依赖注入。概念相同,但名称不同

标签: c# nhibernate dependency-injection inversion-of-control


【解决方案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 :)

【讨论】:

    【解决方案2】:

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

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

    B 和 C:

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

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

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

    我相信您实际上指的是this link 上发布的比较。

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

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

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

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

    【讨论】:

      【解决方案3】:

      菲利普,

      感谢您的回答!

      B 和 C

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

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

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

      答:

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

      谢谢

      马修

      【讨论】:

        【解决方案4】:

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

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

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

        问候

        【讨论】:

        • 是否有需要 DI 的案例 - 您通常可以通过其他方式进行操作,但 DI 的优势不是您将组件解耦......
        【解决方案5】:

        巴勃罗,

        感谢你们的cmets。

        也许如果我详细说明我打算在项目中使用 DI 的一个领域(正如您所说,不仅要了解 DI,而且因为我认为这是必要的),然后可以将进一步的 cmets 制作为是否是使用 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 容器来处理依赖注入的完美示例吗?

        我认为我正在尝试做的事情与this onethis one 等文章中描述的DI/IoC 非常相似。

        最后,我将使用工厂类,可能带有内部/子容器,以便根据参数值动态解析组件/实现器(ConcreteMarginCalculatorA、ConcreteMarginCalculatorB 等...)。为了实现这一点,我倾向于Autofac (http://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 用于的那种场景。 别人怎么看?

        谢谢

        马修

        【讨论】:

        • Matthew - 您可以编辑您的原始问题以包含此内容吗?
        • Mathew - 我可能遗漏了一些东西,但在 TransactionProcessor 中,您不需要产品的特定 marginCalculator,因此只需从 IoC 容器中查找吗?
        【解决方案6】:
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多