1

我有以下代码:

public class DotLessFactory
{
    private LessEngine lessEngine;

    public virtual ILessEngine GetEngine()
    {
        return lessEngine ?? (lessEngine = CreateEngine());
    }

    private ILessEngine CreateEngine()
    {
        var configuration = new LessConfiguration();
        return new LessFactory().CreateEngine(configuration);
    }
}

让我们假设以下内容:

  1. 我总是希望返回相同的 ILessEngine 实例。
  2. 我使用 DI 容器(我将在本示例中使用 StructureMap)来管理我的对象。
  3. 由于假设编号 1,我的对象的生命周期将是 Singleton。
  4. CreateEngine 中的代码执行通过 NuGet 包引用的代码。
  5. DotLess 只是一个例子。此代码可适用于任何类似的 NuGet 包。

方法一

我使用以下方法在我的 DI 容器中注册我的对象:

For<DotLessFactory>().Singleton().Use<DotLessFactory>();
For<ILessEngine>().Singleton().Use(container => container.GetInstance<DotLessFactory>().GetEngine());
For<ISomeClass>().Singleton().Use<SomeClass>();

现在我可以将 ILessEngine 添加到构造函数中,并让我的容器按照下面的代码注入它的一个实例。

public class SomeClass : ISomeClass
{
    private ILessEngine lessEngine;

    public SomeClass(ILessEngine lessEngine)
    {
        this.lessEngine = lessEngine;
    }
}

方法二

引入一个公开 GetEngine 方法的 IDotLessFactory 接口。我使用以下方法在我的 DI 容器中注册我的对象:

For<IDotLessFactory>().Singleton().Use<DotLessFactory>();
For<ISomeClass>().Singleton().Use<SomeClass>();

现在我的工厂将按照下面的代码创建一个 ILessEngine 实例。

public class SomeClass : ISomeClass
{
    private ILessEngine lessEngine;

    public SomeClass(IDotLessFactory factory)
    {
        Factory = factory;
    }

    public IDotLessFactory Factory { get; private set; }

    public ILessEngine LessEngine
    {
        get
        {
            return lessEngine ?? (lessEngine = Factory.GetEngine());
        }
    }
}

我的问题是:

  1. 就 ILessEngine 而言,方法 1 和方法 2 之间的根本区别是什么?在方法 1 中,ILessEngine 由容器管理,而在方法 2 中则不是。每种方法的优点/缺点是什么?一种方法比另一种更好吗?
  2. 我是否需要在 CreateEngine 方法中使用同步锁来确保任何方法的线程安全?在这种情况下,我什么时候应该/不应该使用同步锁?
  3. 我已经看到了在 CreateEngine 方法中使用 Activator.CreateInstance 的示例,而不是直接更新对象。有理由使用这种方法吗?这与不将工厂对象中的直接依赖项引入 NuGet 包内的对象有关吗?
  4. 让我们假设引用的 NuGet 包在后台与 HttpContext 一起工作。在单例范围内注册我的工厂是否会对 HttpContext 产生负面影响,或者这无关紧要,因为我认为 NuGet 包最有可能管理 HttpContext 本身的范围?
  5. 最后,DotLessFactory 最终将与 Bundles(Microsoft.AspNet.Web.Optimization NuGet 包)一起使用,并且 Bundle 仅在应用程序启动时实例化(不由容器管理)。Bundle 将依赖于 DotLessFactory 的注入实例。这个事实对上述问题有什么影响吗?

任何反馈都会非常有帮助。

4

1 回答 1

1

具体回答这些问题并非易事,但请允许我提供一些非详尽的评论:

  1. 据我所知,没有一种方法可以保证要求#1(单例)。这是因为两个线程可以同时执行查找,并且都将 lessEngine 评估为 null 并触发新实例的创建。如果 StructureMap 查找是线程安全的,那么第一种方法最终可能是线程安全的,但如果是这种情况,我会感到惊讶(无论如何,您不希望您的代码依赖于 3rd 方库中的实现“细节” )。

  2. 两种解决方案都犯了同样的错误,本质上是在不保护整个代码区域的情况下检查实例是否已经创建。为了解决这个问题,引入一个私有对象变量来锁定,并保护创建实例的代码区域:

    private object engineLock = new object();
    
    public virtual ILessEngine GetEngine()
    {
        lock( engineLock ) { return lessEngine ?? (lessEngine = CreateEngine()); }
    }
    

    顺便说一句,如果您可以让 StructureMap 处理整个对象链的构造,则不需要这样做,因为这将取决于 StructureMap 来确保根据您的容器配置的单例要求。

  3. 如果您知道新对象具有默认构造函数(例如,通过代码中类型参数的通用约束)或者您有对它们的编译时引用,则只能新建对象。由于 IoC 主要创建它在编译时不知道的东西,并且在这样做时经常需要传递参数,因此使用 Activator.CreateInstance 代替。据我所知,使用“new”生成IL来调用Activator.CreateInstance,所以最终结果都是一样的。

  4. HttpContext 的生命周期在您的应用程序之外(由 ASP.NET)管理,因此不存在范围问题。HttpContext.Current 将被设置或不设置,如果不是,那么你做的工作太早以至于它不可用(或者在它永远不可用的上下文中执行,例如在 ASP.NET 之外) .

  5. 呃,不确定您在这里考虑的是什么潜在问题,但我最好的猜测是它不应该对您的代码产生任何影响。

希望这可以帮助!

于 2013-07-08T18:46:01.553 回答